# SpringSecurity_JWT **Repository Path**: GJW520/SpringSecurity_JWT ## Basic Information - **Project Name**: SpringSecurity_JWT - **Description**: SpringBoot整合 SpringSecurity 和 JWT 实现 前后端分离 Demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2021-07-10 - **Last Updated**: 2022-02-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringSecurity+JWT 实现前后端分离 权限校验 引入依赖 ```xml mysql mysql-connector-java runtime com.baomidou mybatis-plus-boot-starter 3.4.3.1 org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-security cn.hutool hutool-all 5.7.2 io.jsonwebtoken jjwt 0.9.0 org.springframework.boot spring-boot-starter-web ``` # 1 创建生成 Token的工具类 ```java @Component public class JwtUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JwtUtil.class); //token生成日期 private static final String CLAIM_KEY_GENTIME = "genTime"; private static final String CLAIM_KEY_ID = "tokenId"; //加密密钥 private final String secret="B7r%MIBrYCxw91NG471i1Ur2hWVRI&tcBPXd9"; // @Value("${jwt.expire}") private Long expiration = 1000L; /** * 根据负责生成JWT的token */ private String generateToken(Map claims,Date expireDate) { return Jwts.builder() .setClaims(claims) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 从token中获取JWT中的负载 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { //解析token claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { System.out.println("token无效-->"); } return claims; } /** * 从token中获取登录用户名 */ public String getUserNameFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims==null ? "" : claims.getSubject(); } /** * 从token中获取信息 如 CLAIM_KEY_GENTIME , CLAIM_KEY_ID */ public String getInfoFromToken(String token,String key){ Claims claims = getClaimsFromToken(token); return claims==null ? "" : claims.get(key).toString(); } /** * 验证token是否还有效 * * @param token 客户端传入的token * @param sysUser 从数据库中查询出来的用户信息 */ public boolean validateToken(String token, SysUser sysUser) { if(sysUser == null ) return false; String username = getUserNameFromToken(token); return username.equals(sysUser.getUsername()) && !isTokenExpired(token); } /** * 判断token是否已经失效 */ private boolean isTokenExpired(String token) { Date expiredDate = getExpiredDateFromToken(token); return expiredDate.before(new Date()); } /** * 获取 token 的过期时间 */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } /** * 根据用户信息生成token */ public String generateToken(SysUser sysUser) { return generateToken(sysUser,genExpireDate()); } /** * 根据用户信息生成token */ public String generateToken(SysUser sysUser,Date date) { //放入 claims Map claims = new HashMap<>(); //放入一些信息,可以通过 getInfoFromToken()方法取出 claims.put(Claims.SUBJECT, sysUser.getUsername()); claims.put(CLAIM_KEY_GENTIME, new Date()); claims.put(CLAIM_KEY_ID, sysUser.getId()); return generateToken(claims,date); } /** * 判断token是否可以被刷新 */ public boolean canRefresh(String token) { return !isTokenExpired(token); } /** * 刷新token */ public String refreshToken(String token) { Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_GENTIME, new Date()); return generateToken( claims,genExpireDate() ); } private Date genExpireDate(){ return new Date(System.currentTimeMillis() + expiration * 1000); } } ``` # 2 创建 UserDetails 和 UserDetailsService **UserDetails**: 用于获取用户用户名,密码,状态 这里我继承了自己的SysUser(数据库中的用户表) ```java @Data @AllArgsConstructor public class AuthUserDetails extends SysUser implements UserDetails { @Override public Collection getAuthorities() { return getPermissions().stream().filter( p -> StrUtil.isBlank( p.getPermission() ) ) .map( p -> new SimpleGrantedAuthority( p.getPermission() ) ) .collect(Collectors.toList()); } // 账户是否未过期 @Override public boolean isAccountNonExpired() { return 0 == getDeleted(); } //账户是否未锁定 @Override public boolean isAccountNonLocked() { return isAccountNonExpired(); } //凭证是否有效 @Override public boolean isCredentialsNonExpired() { return isAccountNonExpired(); } //账号是否可用 @Override public boolean isEnabled() { return isAccountNonExpired(); } } ``` **UserDetailsService** : 用于通过 username获取UserDetails ```java @Component public class AuthUserDetailsService implements UserDetailsService { @Autowired private SysUserService sysUserService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = sysUserService.getBaseMapper().getUserByUsername(username); return BeanUtil.copyProperties(sysUser,AuthUserDetails.class); } } ``` # 3 创建 JwtTokenFilter 创建一个Filter用于获取token 授权 ```java @Component public class JwtTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private SysUserService sysUserService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = StrUtil.blankToDefault( request.getHeader("token"),request.getParameter("token") ); //如果token不为空 if( StrUtil.isNotBlank( token ) && SecurityContextHolder.getContext().getAuthentication() == null ){ String username = jwtUtil.getUserNameFromToken(token); SysUser sysUser = sysUserService.getBaseMapper().getUserByUsername(username); if( jwtUtil.validateToken(token,sysUser) ){ AuthUserDetails authUserDetails = BeanUtil.copyProperties(sysUser, AuthUserDetails.class); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(token,null,authUserDetails.getAuthorities() ); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request,response); } } ``` 4 创建WebSecurityConfigurerAdapter 配置权限逻辑 ```java @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthUserDetailsService userDetailsService; @Autowired private JwtTokenFilter jwtTokenFilter; @Autowired private RestfulAccessDeniedHandler restfulAccessDeniedHandler; @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); //允许跨域请求的OPTIONS请求 registry.antMatchers(HttpMethod.OPTIONS) .permitAll() .antMatchers(// 允许对于网站静态资源的无授权访问, "/getToken", "/admin/login", "/admin/register" ) .permitAll() .anyRequest()// 除上面外的所有请求全部需要鉴权认证 .authenticated() .and().csrf()// 由于使用的是JWT,我们这里不需要csrf .disable() .sessionManagement()// 基于token,所以不需要session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests(); // 禁用缓存 httpSecurity.headers().cacheControl(); // 添加JWT filter httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); //添加自定义未授权和未登录结果返回 httpSecurity.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthenticationEntryPoint); //配置允许跨域 httpSecurity.cors().configurationSource(corsConfigurationSource()); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService( userDetailsService ) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * 配置 跨域 */ @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); //同源配置,*表示任何请求都视为同源,若需指定ip和端口可以改为如“localhost:8080”,多个以“,”分隔; corsConfiguration.addAllowedHeader("*");//header,允许哪些header,本案中使用的是token,此处可将*替换为token; corsConfiguration.addAllowedMethod("*"); //允许的请求方法,PSOT、GET等 ((UrlBasedCorsConfigurationSource) source).registerCorsConfiguration("/**",corsConfiguration); //配置允许跨域访问的url return source; } } ```