# 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 extends GrantedAuthority> 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;
}
}
```