2021-01-18

需求

1、使用用户名和密码进行用户认证,成功返回token

2、拦截请求,验证token,更新token,返回新token

 

实现思路

生成UsernamePasswordAuthenticationToken,并调用AuthenticationManager的authenticate方法验证用户身份

方案一:

编写一个继承AbstractAuthenticationProcessingFilter的filter。首先在ter里实现一个RequestMatcher,指定拦截的url和http方法。然后重写attemptAuthentication方法,提取用户名和密码,实例化一个UsernamePasswordAuthenticationToken实例,以实例作为参数调用AuthenticationManager的authenticate方法,并返回方法调用的结果。

public class MyUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
    public MyUsernamePasswordAuthenticationFilter() {
         //拦截url为 "/login" 的POST请求
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        //从json中获取username和password
        String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
        String username = null, password = null;
        if(StringUtils.hasText(body)) {
            JSONObject jsonObj = JSON.parseObject(body);
            username = jsonObj.getString("username");
            password = jsonObj.getString("password");
        }   
        
        if (username == null) 
            username = "";
        if (password == null)
            password = "";
        username = username.trim();
       //封装到token中提交
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
        
        return this.getAuthenticationManager().authenticate(authRequest);
    }

}

方案二:

编写一个验证方法,接受参数grant_type和JSONObject。grant_type传入Oauth支持的5类模式名之一,JSONObject传入验证信息。根据grant_type的类型,使用JSONObject生成一个Authentication实现类实例,这里是UsernamePasswordAuthenticationToken。以实例作为参数传入AuthenticationManager的authenticate方法中,返回认证结果。

 

实现UserDetailsService接口

AuthenticationManager的authenticate方法中会遍历执行所有provider的authenticate方法,直到返回结果不为null(为null表示这个provider不适用)。spring security提供了DaoAuthenticationProvider作为默认实现,在这个provider(应该说是所有的provider实现里都调用了UserDetailsService的loadUserByUsername方法,所以需要实现这个接口。

 

认证结果处理:

编写一个类UsernamePasswordProvider继承DaoAuthenticationProvider,重写createSuccessAuthentication。这个方法会在认证通过后调用,在方法里生成token并返回。

public class UsernamePasswordProvider extends DaoAuthenticationProvider {

    final static Integer EXPIRE = 7200;
    final static Integer REFRESH_EXPIRE = 30;
    final String jwtKey;
    final boolean enableExpire;

    public UsernamePasswordProvider(String jwtKey,boolean enableExpire) {
        this.enableExpire = enableExpire;
        this.jwtKey = jwtKey;
    }

    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {

        JwtBuilder refreshBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, Base64Codec.BASE64URL.encode(jwtKey));
                //.compressWith(new GzipCompressionCodec())

        JwtBuilder builder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, Base64Codec.BASE64URL.encode(jwtKey));
                //.compressWith(new GzipCompressionCodec())

        if(enableExpire) {
            builder.setExpiration(Date.from(Instant.now().plusSeconds(EXPIRE)));
            refreshBuilder.setExpiration(Date.from(Instant.now().plus(REFRESH_EXPIRE, ChronoUnit.DAYS)));
        }

        refreshBuilder.setSubject(user.getUsername());
        builder.setSubject(user.getUsername());

        for(var authorize: user.getAuthorities()){
            if(GrantedClaim.class.isAssignableFrom(authorize.getClass())){
                GrantedClaim claim = (GrantedClaim)authorize;
                builder.claim(claim.getName(),claim.getValue());
            }
        }


        return AccessToken.builder()
                .accessToken(builder.compact())
                .refreshToken(refreshBuilder.compact())
                .expire(enableExpire?EXPIRE:-1)
//                .authorities(user.getAuthorities())
                .build();
    }
}

携带token的api验证

编写一个JwtTokenFilter继承OncePerRequestFilter,重写doFilterInternal方法,在方法里解析token,如果token有问题,抛出异常,否则生成一个自定义的Authentication对象,将对象存入SecurityContext中。

@Slf4j
@Order(100)
public class JwtTokenFilter extends OncePerRequestFilter {
    final String jwtKey;

    public JwtTokenFilter(String jwtKey) {
        this.jwtKey = jwtKey;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        String authorization = request.getHeader("Authorization");

        if (authorization == null || !authorization.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        String token = authorization.replace("Bearer ", "");

        try {
            var parse = Jwts.parser()
                    .setAllowedClockSkewSeconds(300) //允许5分钟容差
                    .setSigningKey(Base64Codec.BASE64URL.encode(jwtKey))
                    .parseClaimsJws(token);
            Claims claims = parse.getBody();
            String uid = claims.getOrDefault("uid",0).toString();
            String name = claims.getSubject();
            List<String> authorities = (List) claims.get("authority");

            List<Claim> claimList = new ArrayList<>();
            if(authorities != null){
                claimList = authorities.stream()
                        .map(val -> new Claim("uid", uid, val))
                        .collect(Collectors.toList());
            }


            SecurityContextHolder.getContext().setAuthentication(new AuthorizedToken(uid,name, claimList));
            request.setAttribute("X-UID",uid);
            chain.doFilter(request,response);

        }catch (JwtException ex){
            log.warn(ex.getMessage());
            onUnsuccessfulAuthentication(request,response);
        }
    }


    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.FORBIDDEN.value());
    }
}

 

配置

编写一个SecurityConfig类,继承WebSecurityConfigurerAdapter,使用@Configuration和@EnableWebSecurity注解。使用@Bean注解标注AuthenticationManager和PasswordEncoder的工厂方法,向容器注入这个类的bean实例。重写configure(AuthenticationManagerBuilder auth)方法,在方法中实例化UsernamePasswordProvider,将它的实例和PasswordEncoder、实现UserDetailsService接口的UserService注入到AuthenticationManagerBuilder中。重写configure(HttpSecurity http)方法,调用HttpSecurity的addFilterBefore方法将JwtTokenFilter注册到UsernamePasswordAuthenticationFilter前。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.jwt.signature}")
    String jwtKey;

    @Value("${security.jwt.expire.enable}")
    boolean jwtExpireEnable;


    @Autowired
    UserDetailsService userService;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //auth.authenticationProvider()
//        auth.inMemoryAuthentication()
//                .passwordEncoder(new BCryptPasswordEncoder())
//                .withUser(User.builder().username("vincent").password(new BCryptPasswordEncoder().encode("1234")).roles("ADMIN"));

        var usernamePasswordProvider = new UsernamePasswordProvider(jwtKey,jwtExpireEnable);
        usernamePasswordProvider.setPasswordEncoder(passwordEncoder());
        usernamePasswordProvider.setUserDetailsService(userService);
        auth.authenticationProvider(usernamePasswordProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().disable()
                .csrf().disable()
                .cors()
                .and()
                .authorizeRequests()
                .and()
                .addFilterBefore(new JwtTokenFilter(jwtKey), UsernamePasswordAuthenticationFilter.class);
    }


}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值