SpringSecurity 自定义登陆方式验证user身份发布jwt令牌

巧了我就是萌 提交于 2020-02-27 03:29:19

这里我们已账户密码登录为例

public ResponseEntity<MsgInfo<UserVM>> authenticate(@Valid @RequestBody LoginVM loginVM) {  
    Map<String, String> objects = new HashMap<>();  
    objects.put("password", loginVM.getPassword());  
    final UsernamePasswordAuthenticationToken authenticationToken =  
        new UsernamePasswordAuthenticationToken(loginVM.getUsername(),  
            objects);  
    final Authentication authentication = this.authenticationManager.authenticate(authenticationToken);  
    SecurityContextHolder.getContext().setAuthentication(authentication);  
    final boolean rememberMe = (loginVM.getRememberMe() == null) ? false : loginVM.getRememberMe();  
    final String jwt = tokenProvider.createToken(authentication, rememberMe);  
  
    final UserVM user = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername());  
    if (!loginStatus.equals(user.getStatus())) {  
        throw new RuntimeException("您的账号已被冻结,暂时不能登录.");  
    }  
    final HttpHeaders httpHeaders = new HttpHeaders();  
    httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " \+ jwt);  
    userLoginLogService.insertOne(user);  
    return new ResponseEntity<>(new MsgInfo(StatusCode.ok, user), httpHeaders, HttpStatus.OK);  
}

一、继承org.springframework.security.authentication.AbstractAuthenticationToken,重写部分方法

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {  
  
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL\_VERSION\_UID;  
  
    private Object principal;  
    private Map<String, String> credentials;  
    private Map<String, String> details;  
  
  
    @Override  
  public Object getDetails() {  
        return this.details;  
    }  
  
    public UsernamePasswordAuthenticationToken(Object principal, Map<String, String> credentials) {  
        super(null);  
        this.principal = principal;  
        this.credentials = credentials;  
        setAuthenticated(false);  
    }  
  
    public UsernamePasswordAuthenticationToken(Object principal,  
                                         Map<String, String> credentials,  
                                         Collection<? extends GrantedAuthority> authorities,  
                                         Map<String,String> details) {  
        super(authorities);  
        this.principal = principal;  
        this.credentials = credentials;  
        this.details=details;  
        super.setAuthenticated(true); // must use super, as we override  
  }  
  
    public UsernamePasswordAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {  
        super(authorities);  
        this.principal = principal;  
        this.credentials = null;  
        super.setAuthenticated(true); // must use super, as we override  
  }  
  
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {  
        if (isAuthenticated) {  
            throw new IllegalArgumentException(  
                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");  
        }  
  
        super.setAuthenticated(false);  
    }  
  
    @Override  
  public void eraseCredentials() {  
        super.eraseCredentials();  
        credentials = null;  
    }  
  
  
// public MobileCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {  
//    super(authorities);  
//        this.principal = principal;  
//        this.credentials = null;  
//        super.setAuthenticated(true); // must use super, as we override  
// }  
  
  @Override  
  public Object getCredentials() {  
        // TODO Auto-generated method stub  
  return this.credentials;  
    }  
  
    @Override  
  public Object getPrincipal() {  
        // TODO Auto-generated method stub  
  return this.principal;  
    }  
}

这里相当于一个缓存区域,可以缓存用户信息,其中details是很重要的一个变量。在授权过程中其他信息会被清除,但是details属性回保留下来。

二、实现org.springframework.security.authentication.AuthenticationProvider,编写一个第一步写的token的授权提供者

@Component  
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {  
  
    @Autowired  
  private UserService userService;  
  
    @Autowired  
  private PasswordEncoder passwordEncoder;  
  
    @Autowired  
  private AuthorityMapper authorityMapper;  
  
    @Override  
 @SuppressWarnings("unchecked")  
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {  
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();  
        Map<String, String> params = (Map<String, String>) authentication.getCredentials();  
        String password = params.get("password");  
        if (StringUtils.isEmpty(password)) {  
            throw new BadCredentialsException("密码不能为空");  
        }  
        User user = userService.getUserByAccount(username);  
        if (null == user) {  
            throw new BadCredentialsException("用户不存在");  
        }  
        if (!passwordEncoder.matches(password, user.getPassword())) {  
            throw new BadCredentialsException("用户名或密码不正确");  
        }  
        Map<String, String> details = new HashMap<>(4);  
        details.put("loginType", "password");  
        details.put("login", user.getLogin());  
        details.put("telephone", user.getTelephone());  
        details.put("email", user.getEmail());  
  
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(  
            username,  
            params,  
            listUserGrantedAuthorities(user.getLogin()),  
            details);  
        result.setDetails(authentication.getDetails());  
        return result;  
    }  
  
    @Override  
  public boolean supports(Class<?> authentication) {  
        System.out.println(this.getClass().getName() + "---supports");  
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));  
    }  
  
    private Set<GrantedAuthority> listUserGrantedAuthorities(String login) {  
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();  
        if (StringUtils.isEmpty(login)) {  
            return authorities;  
        }  
        Set<Authority> authority = authorityMapper.selectByUserLogin(login);  
        for (Authority auth : authority) {  
            authorities.add(new SimpleGrantedAuthority(auth.getName()));  
        }  
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));  
        return authorities;  
    }  
  
}

public Authentication authenticate(Authentication authentication)这个方法是springSecurity框架在校验授权的过程中会去调用校验的,经过这个authentication()方法之后就是已经经过了校验了。

public boolean supports(Class<?> authentication) 这个方法需要你把第一步的token和该provider进行匹配。

三、颁发jwt令牌

public String createToken(Authentication authentication, boolean rememberMe) {  
    String authorities = authentication.getAuthorities().stream()  
        .map(GrantedAuthority::getAuthority)  
        .collect(Collectors.joining(","));  
    return this.createToken(authentication.getName(), authentication.getDetails(), authorities, rememberMe);  
}  
  
@SuppressWarnings("unchecked")  
public String createToken(String loginName, Object details, String authorities, boolean rememberMe) {  
    long now = (new Date()).getTime();  
    Date validity;  
    if (rememberMe) {  
        validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe \+ this.tokenValidityInMilliseconds);  
    } else {  
        validity = new Date(now + this.tokenValidityInMilliseconds \* 2);  
    }  
    Map<String, String> params;  
    if (details != null) {  
        params = (Map<String, String>) details;  
    } else {  
        params = new HashMap<>(0);  
    }  
    return Jwts.builder()  
        .setSubject(loginName)  
        .claim(AUTHORITIES_KEY, authorities)  
        .claim("telephone", params.get("telephone"))  
        .claim("email", params.get("email"))  
        .signWith(key, SignatureAlgorithm.HS512)  
        .setExpiration(validity)  
        .compact();  
}

这是一个示范,header里面是加密的方式,payload是其中的一些信息。 所以自定义的一些需要存储到token里面的可以用claim(key,value)来加入其中。

这里稍后博客会说明jwt令牌的构成。

四、客户带着token访问时,在过滤器(Filter)中编写过滤器(分布式就要用zuul网关)

继承org.springframework.web.filter.GenericFilterBean,实现过滤器

public class JWTFilter extends GenericFilterBean {  
  
    public static final String AUTHORIZATION_HEADER = "Authorization";  
  
    private TokenProvider tokenProvider;  
  
    public JWTFilter(TokenProvider tokenProvider) {  
        this.tokenProvider = tokenProvider;  
    }  
  
    @Override  
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)  
        throws IOException, ServletException {  
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;  
        String jwt = resolveToken(httpServletRequest);  
        if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {  
            Authentication authentication = this.tokenProvider.getAuthentication(jwt);  
            SecurityContextHolder.getContext().setAuthentication(authentication);  
        }  
        filterChain.doFilter(servletRequest, servletResponse);  
    }  
  
    private String resolveToken(HttpServletRequest request){  
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);  
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {  
            return bearerToken.substring(7, bearerToken.length());  
        }  
        return null;  
    }  
}

其中包含解析token的,确认token是不是系统办法的

public boolean validateToken(String authToken) {  
    try {  
        Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);  
        return true;  
    } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {  
        log.info("Invalid JWT signature.");  
        log.trace("Invalid JWT signature trace: {}", e);  
    } catch (ExpiredJwtException e) {  
        log.info("Expired JWT token.");  
        log.trace("Expired JWT token trace: {}", e);  
    } catch (UnsupportedJwtException e) {  
        log.info("Unsupported JWT token.");  
        log.trace("Unsupported JWT token trace: {}", e);  
    } catch (IllegalArgumentException e) {  
        log.info("JWT token compact of handler are invalid.");  
        log.trace("JWT token compact of handler are invalid trace: {}", e);  
    }  
    return false;  
}

直接解析token来进行获取到自己编写的token类

public Authentication getAuthentication(String token) {  
    Claims claims = Jwts.parser()  
        .setSigningKey(key)  
        .parseClaimsJws(token)  
        .getBody();  
  
    //在Token过期时间到期后,并在另一个ExpireationTime时间范围内,系统会自动刷新Token,并返回前端  
  long now = new Date().getTime() - ((long) claims.getExpiration().getTime() - this.tokenValidityInMilliseconds);  
    boolean refreshed = false;  
    if (0 < now && now < this.tokenValidityInMilliseconds) {  
        token = refreshToken(claims);  
        refreshed = true;  
    }  
  
    Collection<? extends GrantedAuthority> authorities =  
        Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))  
            .map(SimpleGrantedAuthority::new)  
            .collect(Collectors.toList());  
  
    Map<String, String> properties = new HashMap<>(3);  
    properties.put("telephone", String.valueOf(claims.get("telephone")));  
    properties.put("email", String.valueOf(claims.get("email")));  
  
    AuthenticationUser principal = new AuthenticationUser(claims.getSubject(), "", authorities, properties);  
  
    CommonAuthenticationToken authenticationToken = new CommonAuthenticationToken(  
        principal,  
        token,  
        authorities);  
    authenticationToken.setDetails(refreshed);  
    return authenticationToken;  
}

SecurityContextHolder.getContext().setAuthentication(authentication); 就是将该登录客户放入缓存中。

五、进行springSecurity配置适配器进行

写一个继承自org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的类

@Configuration  
@EnableWebSecurity  //springSecurity自带注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)  //使用该配置进行
@Import(SecurityProblemSupport.class)  
@Order(1000)  //放到后面再加载
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {  
  
    private final AuthenticationManagerBuilder authenticationManagerBuilder;  
  
    private final UserDetailsService userDetailsService;  
  
    private final TokenProvider tokenProvider;  
  
    private final CorsFilter corsFilter;  
  
    private final SecurityProblemSupport problemSupport;  
  
    @Autowired  
 @Qualifier("authenticationManagerBean")  
    private AuthenticationManager authenticationManager;  
  
    @Autowired  //用户名和密码登录
  private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;  
    @Autowired  //验证码登录
  private MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider;  
  
  
    @Override  
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
        auth  //这里是将我们写好的provider注入到springSecurity框架中
		//该地放provider的顺序是框架springSecurity调用authentication()的顺序
		//只要有一个provider就可以通过就可以,全部不通过就算是全部失败,验证失败
                .authenticationProvider(mobileCodeAuthenticationProvider)  
                .authenticationProvider(usernamePasswordAuthenticationProvider)  
                ;  
    }  
  
  
    public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {  
        this.authenticationManagerBuilder = authenticationManagerBuilder;  
        this.userDetailsService = userDetailsService;  
        this.tokenProvider = tokenProvider;  
        this.corsFilter = corsFilter;  
        this.problemSupport = problemSupport;  
    }  
  
    @PostConstruct  
  public void init() {  
        try {  
            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();  
            authenticationProvider.setHideUserNotFoundExceptions(false);  
            authenticationProvider.setUserDetailsService(userDetailsService);  
            authenticationProvider.setPasswordEncoder(passwordEncoder());  
            authenticationManagerBuilder  
  .authenticationProvider(authenticationProvider);  
        } catch (Exception e) {  
            throw new BeanInitializationException("Security configuration failed", e);  
        }  
    }  
  
    @Override  
 @Bean  public AuthenticationManager authenticationManagerBean() throws Exception {  
        AuthenticationManager authenticationManager = super.authenticationManagerBean();  
        return authenticationManager;  
    }  
  
    @Bean  //这个是密码加密器,不可逆转
  public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
  
    @Override  
  public void configure(WebSecurity web) throws Exception {  
        web.ignoring()  
                .antMatchers(HttpMethod.OPTIONS, "/**")  
                .antMatchers("/app/**/*.{js,html}")  
                .antMatchers("/i18n/**")  
                .antMatchers("/content/**")  
                .antMatchers("/swagger-ui/index.html")  
                .antMatchers("/test/**");  
    }  
  
    @Override  
  public void configure(HttpSecurity http1) throws Exception {  
        ExceptionHandlingConfigurer<HttpSecurity> http = http1  
                .csrf()  
                .disable()  
                .addFilterBefore(bhAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)  
                .exceptionHandling()  
                .authenticationEntryPoint(problemSupport)  
                .accessDeniedHandler(problemSupport);  
        http.and()  
                .headers()  
                .frameOptions()  
                .disable();  
        http.and()  
                .sessionManagement()  
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);  
        http.and()  
                .authorizeRequests()  
                .antMatchers("/api/register").permitAll()  
                .antMatchers("/api/activate").permitAll()  
                .antMatchers("/api/authenticate").permitAll()  
                .antMatchers("/api/account/reset-password/init").permitAll()  
                .antMatchers("/api/account/reset-password/finish").permitAll()  
                .antMatchers("/management/health").permitAll()  
                .antMatchers("/management/info").permitAll()  
                .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);  
        http.and()  
                .authorizeRequests()  
                .antMatchers("/api/**").permitAll();        //关闭权限验证  
 //.antMatchers("/api/**").authenticated(); //打开权限验证  http.and()  
                .apply(securityConfigurerAdapter());  
  
    }  
  
    @Bean  
  public BhAuthenticationFilter bhAuthenticationFilter() {  
        BhAuthenticationFilter filter = new BhAuthenticationFilter();  
        filter.setAuthenticationManager(authenticationManager);  
        return filter;  
    }  
  
  
    private JWTConfigurer securityConfigurerAdapter() {  
        return new JWTConfigurer(tokenProvider);  
    }  
}

到此,springSecurity已经完成jwt令牌的颁发和校验。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!