单独看代码可能会比较难理解,大家可以先看我前一篇文章,先了解大致原理,对源码有一定的了解后再看下面代码,点击查看springboot整合security做JWT登录认证。
1.准备工作
- 新搭建springboot项目,导入需要用到的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
- 数据库使用的mysql,新建一个测试数据库并创建表user用来做测试,sql如下:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT ,
`username` varchar(32) NULL COMMENT '登录名' ,
`password` varchar(128) NULL COMMENT '密码' ,
PRIMARY KEY (`id`)
)
;
insert into user values (1,'test','MTIzNDU2');
2. 自定义过滤器
过滤器拦截登录请求,并获取到登录信息封装成security认证信息对象UsernamePasswordAuthenticationToken,代码如下:
public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public MyUsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login","POST"));
}
@Override
public void afterPropertiesSet() {
Assert.notNull(getAuthenticationManager(), "authenticationManager must be specified");
Assert.notNull(getSuccessHandler(), "AuthenticationSuccessHandler must be specified");
Assert.notNull(getFailureHandler(), "AuthenticationFailureHandler must be specified");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String req = StreamUtils.copyToString(request.getInputStream(),Charset.forName("utf-8"));
String username = "";
String password = "";
if(StringUtils.hasText(req)){
JSONObject jsonObject = JSONObject.parseObject(req);
username = jsonObject.getString("username");
password = jsonObject.getString("password");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
return this.getAuthenticationManager().authenticate(authenticationToken);
}
}
从之前的文章我们知道,接下来是用户认证管理类AuthenticationManager对登录信息进行校验,然后真正校验操作是交给了Provider去处理,接下来我们自定义provider实现类去校验我们的登录信息。
3. 自定义Provider完成登录信息校验
通过之前的provider分析,我们知道了provider实现登录信息校验的方法是authenticate()方法,同时在AuthenticationManager校验用户信息使用provider的选择是根据登录信息类来判断的,我们自定义Provider的时候,需要实现supports方法来保证我们自定义的provider被使用,详细代码如下:
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Autowired
SelfUserService selfUserService;
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
if(username == null || password==null){
throw new BadCredentialsException("用户名,密码不能为空");
}
try {
UserDetails userDetails = selfUserService.loadUserByUsername(username);
if(userDetails == null){
throw new UsernameNotFoundException("用户名不存在");
}
if(!SelfPasswordEncod.encode(password).equals(userDetails.getPassword())){
throw new BadCredentialsException("用户名或密码错误,请核对后重试");
}
}catch (UsernameNotFoundException e){
throw new UsernameNotFoundException("用户名不存在");
}
return authentication;
}
}
在Provider校验用户信息的时候,我们用到了自定义的UserDetailService实现类和UserDetails实现类
SelfUserDetails
public class SelfUserDetails implements UserDetails {
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
SelfUserService
@Component
public class SelfUserService implements UserDetailsService {
//HS256算法生成token秘钥
public static final String KEY = "test-001";
//token有效期,设为半个钟
public static final int EXPIRE_TIME = 1800000;
@Autowired
UserMapper userMapper;
/**
* 根据用户名获取用户信息
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectByName(username);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
SelfUserDetails userDetails = new SelfUserDetails();
userDetails.setUsername(user.getUsername());
userDetails.setPassword(user.getPassword());
return userDetails;
}
/**
* 将用户信息封装成用户认证jwt
* @param username
* @return
*/
public String getUserToken(String username) {
Date expireTime = new Date(System.currentTimeMillis()+EXPIRE_TIME);
String token = JWT.create().withSubject(username)
.withIssuedAt(new Date())
.withExpiresAt(expireTime)
.sign(Algorithm.HMAC256(KEY));
return token;
}
}
密码加密算法用的base64加密
public class SelfPasswordEncod {
public static String encode(String password){
BASE64Encoder base64Encoder=new BASE64Encoder();
String encodePwd = base64Encoder.encode(password.getBytes());
return encodePwd;
}
}
4. 自定义登录失败与登录成功的方法Handler
登录成功 JsonLoginSuccessHandler
登录成功,将token以json格式传给客户端。
@Component
public class JsonLoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private SelfUserService selfUserService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String token = selfUserService.getUserToken((String) authentication.getPrincipal());
BaseResponse<String> successRes = BaseResponse.ok(token);
response.getWriter().write(JSON.toJSONString(successRes));
}
}
登录失败 LoginFailureHandler
登录失败,我们将错误码统一设为401,把异常信息返回给客户端。
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
BaseResponse loginResponse = new BaseResponse();
loginResponse.setCode(HttpStatus.UNAUTHORIZED.value());
loginResponse.setMessage(exception.getMessage());
//设置响应编码为utf-8,避免中文响应乱码
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSON.toJSONString(loginResponse));
}
}
5. 登录配置类
public class LoginConfigure<T extends LoginConfigure<T, B>, B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<T, B> {
private MyUsernamePasswordAuthenticationFilter authenticationFilter;
//引入我们自定义的过滤器
public LoginConfigure() {
this.authenticationFilter = new MyUsernamePasswordAuthenticationFilter();
}
//配置过滤器相关配置
@Override
public void configure(B http) throws Exception {
authenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
authenticationFilter.setAuthenticationFailureHandler(new LoginFailureHandler());
MyUsernamePasswordAuthenticationFilter filter = postProcess(authenticationFilter);
http.addFilterAfter(filter,LogoutFilter.class);
}
// 设置登录成功处理器
public LoginConfigure<T,B> loginSuccessHandler(AuthenticationSuccessHandler authSuccessHandler){
authenticationFilter.setAuthenticationSuccessHandler(authSuccessHandler);
return this;
}
}
6. Security Web配置
@Configuration
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
@Autowired
private JsonLoginSuccessHandler jsonLoginSuccessHandler;
@Autowired
private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().disable()//禁用表单登录
.cors()
.and()
.apply(new LoginConfigure<>()).loginSuccessHandler(jsonLoginSuccessHandler)
.and()
.sessionManagement().disable();//禁用session
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(usernamePasswordAuthenticationProvider);
}
}
至此springboot 整合security做登录认证已经完成。
授权操作代码实现也已更新,大家可以查看我下一篇博文,传送门。
版权声明:本文为qq_41498815原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。