Spring Security +JWT 登录身份信息验证

  1. 加入依赖包
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>
  1. 单独新建对象保存用户信息(并且实现UserDetails接口)
public class CustomUserDetails implements UserDetails {

    private User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO: get authorities
        return AuthorityUtils.commaSeparatedStringToAuthorityList("");
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
  1. 新建对象实现UserDetailsService接口,自定义加载用户方法,本案例只传入一个固定账号密码。[ 会配置多账号的系统,可以通过username查表获取用户信息,使用该用户的用户id、密码来new User() ]
@Service
public class CustomUserDetailsServiceImpl implements UserDetailsService {

    /**
     * 加载用户
     *
     * @param userName username用户名
     * @return CustomUserDetails
     * @throws UsernameNotFoundException 找不到用户异常
     */
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        if (userName.equals("root")) {
            User user = new User(userName, new BCryptPasswordEncoder().encode("123456"), new ArrayList<>());
            return new CustomUserDetails(user);
        }
        throw new UsernameNotFoundException(userName);
    }
}
  1. 创建jwt相关 --> token过滤器以及匿名用户访问无权限资源时问题

 - token过滤器

 public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String jwt = resolveToken(request);

        if (jwt != null && !"".equals(jwt.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) {
            if (JwtTokenUtils.validateToken(jwt)) {
                /**
                 * get auth info
                 */
                Authentication authentication = JwtTokenUtils.getAuthentication(jwt);
                /**
                 * save user info to securityContext
                 */
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(request, response);
    }

    /**
     * Get token from header
     */
    private String resolveToken(HttpServletRequest request) {
        String token = request.getHeader("token");
        if (StrUtil.isNotEmpty(token)) {
            return token;
        }
        return null;
    }
}

 - 匿名用户访问无权限资源时问题

@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest,
                         HttpServletResponse httpServletResponse,
                         AuthenticationException e) throws IOException {
        log.error("Responding with unauthorized error. Message - {}", e.getMessage());
        httpServletResponse.setHeader("content-type", "application/json;charset=UTF-8");
        httpServletResponse.setStatus(200);
        Result failure = Result.failed(e.getLocalizedMessage());
        failure.setCode(-2);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.print(JSON.toJSONString(failure));
        writer.close();
    }
}
  1. 创建jwt工具类,包含获取token以及token验证
@UtilityClass
@Slf4j
public class JwtTokenUtils {
    private static final String AUTHORITIES_KEY = "vvv";

    /**
     * secret key
     */
    private static final String secretKey = "vvv";

    /**
     * Token validity time(ms)
     */
    private static final long tokenValidityInMilliseconds = 1000 * 60 * 30L;

    /**
     * Create token
     *
     * @param authentication auth info
     * @return token
     */
    public String createToken(Authentication authentication) {
        /**
         * Current time
         */
        long now = (new Date()).getTime();
        /**
         * Validity date
         */
        Date validity;
        validity = new Date(now + tokenValidityInMilliseconds);

        /**
         * create token
         */
        return Jwts.builder()
                .setSubject(authentication.getName())
                .claim(AUTHORITIES_KEY, "")
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    /**
     * Create token
     *
     * @param name auth info
     * @return token
     */
    public String createToken(String name) {
        /**
         * Current time
         */
        long now = (new Date()).getTime();
        /**
         * Validity date
         */
        Date validity;
        validity = new Date(now + tokenValidityInMilliseconds);

        /**
         * create token
         */
        return Jwts.builder()
                .setSubject(name)
                .claim(AUTHORITIES_KEY, "")
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    /**
     * Get auth Info
     *
     * @param token token
     * @return auth info
     */
    public Authentication getAuthentication(String token) {
        /**
         *  parse the payload of token
         */
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();

        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));


        User principal = new User(claims.getSubject(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, "", authorities);
    }

    /**
     * validate token
     *
     * @param token token
     * @return whether valid
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (SignatureException e) {
            log.info("Invalid JWT signature.");
            log.trace("Invalid JWT signature trace: {}", e);
        } catch (MalformedJwtException e) {
            log.info("Invalid JWT token.");
            log.trace("Invalid JWT token 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;
    }
}
  1. 创建配置类WebSecurityConfig,配置和前端对接接口访问权限,以及用户信息
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/login/*").permitAll()
                .anyRequest().authenticated().and()
                // custom token authorize exception handler
                .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler).and()
                // since we use jwt, session is not necessary
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                // since we use jwt, csrf is not necessary
                .csrf().disable();
        http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        //关闭自带登录
        http.formLogin().disable();

        //关闭自带登出
        http.logout().disable();
        // disable cache
        http.headers().cacheControl();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  1. 登录接口实现
public Result login(@RequestParam(value = "username") @NotNull(message = "用户名不能为空") String username,
                    @RequestParam(value = "password") @NotNull(message = "密码不能为空") String password) {
    log.info("用户登录 username:{}, password:{}", username, password);
    if(StrUtil.isEmpty(username) || StrUtil.isEmpty(password)){
        return Result.failed("您输入的账户或密码有误,请重新输入");
    }
    // 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

    try {
        //通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        //将 Authentication 绑定到 SecurityContext
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成Token
        String token = JwtTokenUtils.createToken(authentication);
        return Result.ok(token);
    } catch (BadCredentialsException authentication) {
        authentication.printStackTrace();
        return Result.failed("账号或密码错误,请重新登录");
    }
}
发布了8 篇原创文章 · 获赞 1 · 访问量 3850

猜你喜欢

转载自blog.csdn.net/Mensonge/article/details/104658685
今日推荐