快速上手spring security的三篇文章二:JWT登录认证代码实现

单独看代码可能会比较难理解,大家可以先看我前一篇文章,先了解大致原理,对源码有一定的了解后再看下面代码,点击查看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版权协议,转载请附上原文出处链接和本声明。