Skip to content

Commit

Permalink
前后端分离后台菜单权限控制
Browse files Browse the repository at this point in the history
  • Loading branch information
chao committed Dec 13, 2021
1 parent d2185f2 commit b54f4e0
Show file tree
Hide file tree
Showing 16 changed files with 500 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.ArrayList;

/**
* 自定义身份认证验证组件
*
Expand Down Expand Up @@ -46,12 +43,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
if (bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
log.info("用户登录成功,username={}", name);
// 这里设置权限和角色
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
authorities.add( new GrantedAuthorityImpl("ROLE_ADMIN"));
authorities.add( new GrantedAuthorityImpl("AUTH_WRITE"));
// 生成令牌 这里令牌里面存入了:name,password,authorities, 当然你也可以放其他内容
return new UsernamePasswordAuthenticationToken(name, password, authorities);
return new UsernamePasswordAuthenticationToken(name, password, userDetails.getAuthorities());
} else {
throw new BadCredentialsException("密码错误");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ public class ConstantKey {
* 签名key
*/
public static final String SIGNING_KEY = "Charles@Jwt!&Secret^#";
/**
* Token存入header的键名
*/
public static final String TOKEN_NAME = "Authorization";
/**
* Token过期时间(分钟)
*/
public static final Integer TOKEN_EXPIRE = 5;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package com.example.jwt.config.security;

import com.example.jwt.service.RedisService;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

/**
* 自定义JWT认证过滤器
Expand All @@ -32,117 +27,33 @@
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

private final RedisService redisService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, RedisService redisService) {
private final JwtService jwtService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtService jwtService) {
super(authenticationManager);
this.redisService = redisService;
this.jwtService = jwtService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request, response);
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request, HttpServletResponse response) {
/*
* 解析token
*/
String token = request.getHeader("Authorization");
if (StringUtils.hasLength(token)) {
String cacheToken = String.valueOf(redisService.get(token));
if (StringUtils.hasLength(token) && !"null".equals(cacheToken)) {
String user = null;
try {
Claims claims = Jwts.parser()
// 设置生成token的签名key
.setSigningKey(ConstantKey.SIGNING_KEY)
// 解析token
.parseClaimsJws(cacheToken).getBody();
// 取出用户信息
user = claims.getSubject();
// 重设Redis超时时间
resetRedisExpire(token, claims);
} catch (ExpiredJwtException e) {
log.info("Token过期续签,ExpiredJwtException={}", e.getMessage());
Claims claims = e.getClaims();
// 取出用户信息
user = claims.getSubject();
// 刷新Token
refreshToken(token, claims);
} catch (UnsupportedJwtException e) {
log.warn("访问[{}]失败,UnsupportedJwtException={}", request.getRequestURI(), e.getMessage());
} catch (MalformedJwtException e) {
log.warn("访问[{}]失败,MalformedJwtException={}", request.getRequestURI(), e.getMessage());
} catch (SignatureException e) {
log.warn("访问[{}]失败,SignatureException={}", request.getRequestURI(), e.getMessage());
} catch (IllegalArgumentException e) {
log.warn("访问[{}]失败,IllegalArgumentException={}", request.getRequestURI(), e.getMessage());
}
if (user != null) {
// 获取用户权限和角色
String[] split = user.split("-")[1].split(",");
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
for (String s : split) {
authorities.add(new GrantedAuthorityImpl(s));
}
// 返回Authentication
return new UsernamePasswordAuthenticationToken(user, null, authorities);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// 解析token
String userinfo = jwtService.parseToken(request);
if (userinfo != null) {
// 获取用户权限和角色
String[] split = userinfo.split("-")[1].split(",");
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
for (String s : split) {
authorities.add(new GrantedAuthorityImpl(s));
}
// 返回Authentication
return new UsernamePasswordAuthenticationToken(userinfo, null, authorities);
}
log.warn("访问[{}]失败,需要身份认证", request.getRequestURI());
return null;
}

/**
* 重设Redis超时时间
* 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间)
*/
private void resetRedisExpire(String token, Claims claims) {
// 当前时间
long current = System.currentTimeMillis();
// token签发时间
long issuedAt = claims.getIssuedAt().getTime();
// token过期时间
long expiration = claims.getExpiration().getTime();
// 当前时间 + (`cacheToken`过期时间 - `cacheToken`签发时间)
long expireAt = current + (expiration - issuedAt);
// 重设Redis超时时间
redisService.expire(token, expireAt);
}

/**
* 刷新Token
* 刷新Token的时机: 当cacheToken已过期 并且Redis在有效期内
* 重新生成Token并覆盖Redis的v值(这时候k、v值不一样了),然后设置Redis过期时间为:新Token过期时间
*/
private void refreshToken(String token, Claims claims) {
// 当前时间
long current = System.currentTimeMillis();
/*
* 重新生成token
*/
Calendar calendar = Calendar.getInstance();
// 设置签发时间
calendar.setTime(new Date());
Date now = calendar.getTime();
// 设置过期时间: 5分钟
calendar.add(Calendar.MINUTE, 5);
Date time = calendar.getTime();
String refreshToken = Jwts.builder()
.setSubject(claims.getSubject())
// 签发时间
.setIssuedAt(now)
// 过期时间
.setExpiration(time)
// 算法与签名(同生成token):这里算法采用HS512,常量中定义签名key
.signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
.compact();
// 将refreshToken覆盖Redis的v值,并设置超时时间为refreshToken过期时间
redisService.set(token, refreshToken, time);
// 打印日志
log.info("刷新token执行时间: {}", (System.currentTimeMillis() - current) + " 毫秒");
}
}
42 changes: 12 additions & 30 deletions src/main/java/com/example/jwt/config/security/JwtLoginFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import com.alibaba.fastjson.JSON;
import com.example.jwt.entity.ResponseJson;
import com.example.jwt.service.RedisService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
Expand Down Expand Up @@ -35,10 +32,10 @@
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

private final AuthenticationManager authenticationManager;
private final RedisService redisService;
public JwtLoginFilter(AuthenticationManager authenticationManager, RedisService redisService) {
private final JwtService jwtService;
public JwtLoginFilter(AuthenticationManager authenticationManager, JwtService jwtService) {
this.authenticationManager = authenticationManager;
this.redisService = redisService;
this.jwtService = jwtService;
}

/**
Expand All @@ -65,37 +62,22 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
for (GrantedAuthority grantedAuthority : authorities) {
roleList.add(grantedAuthority.getAuthority());
}
/*
* 生成token
*/
Calendar calendar = Calendar.getInstance();
// 设置签发时间
calendar.setTime(new Date());
Date now = calendar.getTime();
// 设置过期时间: 5分钟
calendar.add(Calendar.MINUTE, 5);
Date time = calendar.getTime();
String token = Jwts.builder()
.setSubject(auth.getName() + "-" + roleList)
// 签发时间
.setIssuedAt(now)
// 过期时间
.setExpiration(time)
// 自定义算法与签名:这里算法采用HS512,常量中定义签名key
.signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
.compact();
// 将token存入redis,并设置超时时间为token过期时间
redisService.set(token, token, time);
// 生成token
String token = jwtService.createToken(auth.getName(), roleList);
/*
* 返回token
*/
log.info("用户登录成功,生成token={}", token);
// 登录成功后,返回token到header里面
response.addHeader("Authorization", token);
// 登录成功后,返回token到body里面
ResponseJson<String> result = ResponseJson.success("登录成功", token);
// 设置用户信息及token到body里面
Map<String, Object> resultMap = new HashMap();
resultMap.put("username",auth.getName());
resultMap.put("token",token);
resultMap.put("roles",roleList);
ResponseJson<Map<String, Object>> resultJson = ResponseJson.success("登录成功", resultMap);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSON.toJSONString(result));
response.getWriter().write(JSON.toJSONString(resultJson));
} catch (IOException e) {
log.error("IOException:", e);
}
Expand Down
Loading

0 comments on commit b54f4e0

Please sign in to comment.