目录
1配置
根据请求头去判断,走小程序,还是Pc端。
#jwt
jwt:
header: Authorization
#小程序前缀 请求头
mini-program-header: MiAuthorization
# 令牌前缀
token-start-with: Bearer
secret: k09BQnaF
# 必须使用最少88位的Base64对该令牌进行编码
base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
# 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
token-validity-in-seconds: 14400000
# 在线用户key
online-key: online-token
# 小程序在线用户
mi-online-key: mi-online-token
# 验证码
code-key: code-key
2.登录
登录时,设置用户信息存储到redis中。
/***
* 根据账户,密码 登录
* @param exUser
* @return
*/
@Override
public R<Object> VxLogin(ExUser exUser) {
// 查询数据库中的账号密码是否存在
ExUser exUserA = exUserMapper.selectOne(
new LambdaQueryWrapper<ExUser>()
.eq(StringUtils.isNotBlank(exUser.getUserUsername()), ExUser::getUserUsername, exUser.getUserUsername())
.eq(StringUtils.isNotBlank(exUser.getUserPassword()), ExUser::getUserPassword, exUser.getUserPassword())
.eq(Objects.nonNull(exUser.getUserStatus()), ExUser::getUserStatus, 1)
);
if (Objects.isNull(exUserA)) {
return R.error("该用户未存在");
}
// 生成token
String token = tokenUtil.generateTokenA(exUserA);
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", exUserA);
}};
RedisUtil.set(properties.getMiOnlineKey() + token, exUserA, properties.getTokenValidityInSeconds() / 1000);
return R.success(authInfo);
}
3.过滤器
获取请求头,判断是小程序接口还是Pc端接口。
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.modules.security.security;
import co.yixiang.domain.ExUser;
import co.yixiang.modules.security.config.SecurityProperties;
import co.yixiang.modules.security.service.OnlineUserService;
import co.yixiang.modules.user.vo.OnlineUser;
import co.yixiang.utils.SpringContextHolder;
import co.yixiang.utils.StringUtils;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @author /
*/
@Slf4j
public class TokenFilter extends GenericFilterBean {
@Autowired
private SecurityProperties securityProperties;
private final TokenUtil tokenUtil;
TokenFilter(TokenUtil tokenUtil) {
this.tokenUtil = tokenUtil;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String requestRri = httpServletRequest.getRequestURI();
OnlineUser onlineUser = null;
ExUser exUser = null;
String authToken = null;
String authTokenA = null;
try {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class);
String clientType = httpServletRequest.getHeader(properties.getHeader());
String miniProgramClientType = httpServletRequest.getHeader(properties.getMiniProgramHeader());
// 检查是否存在PC端或小程序端的请求头
if (clientType == null && miniProgramClientType == null) {
log.error("Both Client-Type and Mini-Program-Client-Type headers are missing.");
filterChain.doFilter(httpServletRequest, servletResponse);
return;
}
if (StringUtils.isNotBlank(clientType)) {
// PC端
authToken = tokenUtil.getToken(httpServletRequest);
} else if (StringUtils.isNotBlank(miniProgramClientType)) {
// 小程序
authTokenA = tokenUtil.getTokenA(httpServletRequest);
}
if (authToken == null && authTokenA == null) {
log.error("Both authToken and authTokenA are null.");
filterChain.doFilter(httpServletRequest, servletResponse);
return;
}
if (StringUtils.isNotBlank(authToken)) {
onlineUser = onlineUserService.getOne(properties.getOnlineKey() + authToken);
} else if (StringUtils.isNotBlank(authTokenA)) {
exUser = onlineUserService.getOneA(properties.getMiOnlineKey() + authTokenA);
// String userJson = RedisUtil.get("userA");
// ExUser user = JSON.parseObject(userJson, ExUser.class);
}
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
// Pc
String username = StringUtils.isNotBlank(authToken) ? tokenUtil.getUsernameFromToken(authToken) : null;
// 小程序
String usernameA = StringUtils.isNotBlank(authTokenA) ? tokenUtil.getUsernameFromToken(authTokenA) : null;
if (onlineUser != null && username != null && SecurityContextHolder.getContext().getAuthentication() == null && tokenUtil.validateToken(authToken)) {
UserDetails userDetails = tokenUtil.getUserDetails(authToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
}else if(exUser != null && usernameA != null && SecurityContextHolder.getContext().getAuthentication() == null && tokenUtil.validateTokenA(authTokenA)){
UserDetails userDetailsA = tokenUtil.getUserDetailsA(authTokenA);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetailsA, null, null);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}else {
tokenUtil.removeToken(authToken);
tokenUtil.removeToken(authTokenA);
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(httpServletRequest, servletResponse);
}
}
4. 工具类完善
package co.yixiang.modules.security.security;
import co.yixiang.domain.ExUser;
import co.yixiang.modules.security.config.SecurityProperties;
import co.yixiang.modules.security.security.vo.JwtUser;
import co.yixiang.utils.RedisUtils;
import co.yixiang.utils.StringUtils;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
/**
* Token 工具类
*
* @author lioncity
*/
@Component
public class TokenUtil {
@Autowired
private SecurityProperties properties;
/**
* Logger
*/
protected static final Logger LOGGER = LoggerFactory.getLogger(TokenUtil.class);
/**
* 权限缓存前缀
*/
private static final String REDIS_PREFIX_AUTH = "auth:";
/**
* 小程序权限缓存前缀
*/
private static final String REDIS_PREFIX_AUTH_A = "mi-auth:";
/**
* 用户信息缓存前缀
*/
private static final String REDIS_PREFIX_USER = "user-details:";
/**
* 小程序 用户信息缓存前缀
*/
private static final String REDIS_PREFIX_USER_A = "mi-user-details:";
/**
* redis repository
*/
@Autowired
private RedisUtils redisUtils;
/**
* 获取用户名
*
* @param token Token
* @return String
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getSubject() : null;
}
/**
* 获取过期时间
*
* @param token Token
* @return Date
*/
public Date getExpiredFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getExpiration() : null;
}
/**
* 获得 Claims
*
* @param token Token
* @return Claims
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(properties.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.warn("getClaimsFromToken exception", e);
claims = null;
}
return claims;
}
/**
* 计算过期时间
*
* @return Date
*/
private Date generateExpired() {
return new Date(System.currentTimeMillis() + properties.getTokenValidityInSeconds() * 1000);
}
/**
* 判断 Token 是否过期
*
* @param token Token
* @return Boolean
*/
private Boolean isTokenExpired(String token) {
Date expirationDate = getExpiredFromToken(token);
return expirationDate.before(new Date());
}
/**
* 生成 Token
*
* @param userDetails 用户信息
* @return String
*/
public String generateToken(UserDetails userDetails) {
String secret=properties.getSecret();
String token = Jwts.builder()
.setSubject(userDetails.getUsername())
.setExpiration(generateExpired())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
String key = REDIS_PREFIX_AUTH + userDetails.getUsername() + ":" + token;
redisUtils.set(key, token, properties.getTokenValidityInSeconds() / 1000);
putUserDetails(userDetails);
return token;
}
public String generateTokenA(ExUser exUser) {
String secret=properties.getSecret();
String token = Jwts.builder()
.setSubject(exUser.getUserUsername())
.setExpiration(generateExpired())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
String key = REDIS_PREFIX_AUTH_A + exUser.getUserUsername() + ":" + token;
redisUtils.set(key, token, properties.getTokenValidityInSeconds() / 1000);
putUserDetailsA(exUser);
return token;
}
/**
* 验证 Token
*
* @param token Token
* @return Boolean
*/
public Boolean validateToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
Object data = redisUtils.get(key);
String redisToken = data == null ? null : data.toString();
return StringUtils.isNotEmpty(token) && !isTokenExpired(token) && token.equals(redisToken);
}
/**
* 验证 Token
*
* @param token Token
* @return Boolean
*/
public Boolean validateTokenA(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH_A + username+ ":" + token;
Object data = redisUtils.get(key);
String redisToken = data == null ? null : data.toString();
return StringUtils.isNotEmpty(token) && !isTokenExpired(token) && token.equals(redisToken);
}
/**
* 移除 Token
*
* @param token Token
*/
public void removeToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
redisUtils.del(key);
delUserDetails(username);
}
/**
* 获得用户信息 Json 字符串
*
* @param token Token
* @return String
*/
protected String getUserDetailsString(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_USER + username;
Object data = redisUtils.get(key);
return data == null ? null : data.toString();
}
/**
* 小程序 获得用户信息 Json 字符串
*
* @param token Token
* @return String
*/
protected String getUserDetailsStringA(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_USER_A + username;
Object data = redisUtils.get(key);
return data == null ? null : data.toString();
}
/**
* 获得用户信息
*
* @param token Token
* @return UserDetails
*/
public UserDetails getUserDetails(String token) {
String userDetailsString = getUserDetailsString(token);
if (userDetailsString != null) {
return new Gson().fromJson(userDetailsString, JwtUser.class);
}
return null;
}
/**
* 小程序 获得用户信息
*
* @param token Token
* @return UserDetails
*/
public UserDetails getUserDetailsA(String token) {
String userDetailsString = getUserDetailsStringA(token);
if (userDetailsString != null) {
return new Gson().fromJson(userDetailsString, ExUser.class);
}
return null;
}
/**
* 存储用户信息
*
* @param userDetails 用户信息
*/
private void putUserDetails(UserDetails userDetails) {
String key = REDIS_PREFIX_USER + userDetails.getUsername();
redisUtils.set(key, new Gson().toJson(userDetails), properties.getTokenValidityInSeconds() / 1000);
}
private void putUserDetailsA(ExUser exUser) {
String key = REDIS_PREFIX_USER_A + exUser.getUserUsername();
redisUtils.set(key, new Gson().toJson(exUser), properties.getTokenValidityInSeconds() / 1000);
}
/**
* 删除用户信息
*
* @param username 用户名
*/
private void delUserDetails(String username) {
String key = REDIS_PREFIX_USER + username;
redisUtils.del(key);
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
public String getTokenA(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getMiniProgramHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
public static void main(String[] args) {
String key = Base64.getEncoder().encodeToString("123".getBytes());
Claims claims = Jwts.parser().setSigningKey(key)
.parseClaimsJws("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFwcCIsIndyaXRlIl0sInVpbiI6MSwiZXhwIjoxNTc1MDE1ODgzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjdiYjQ1NTQtNTQ4OS00YTg5LWI3NjQtNzNjODI0YzljNGMyIiwiY2xpZW50X2lkIjoibHZoYWliYW8ifQ.x7QZxRAR1wuX_YNLi6EzRJ1iaKr1rIEUgjtYF0oSx5k").getBody();
System.out.println(JSON.toJSONString(claims));
}
}
5. 获取当前用户数据的工具
判断该接口是否被类实现
/**
* Copyright (C) 2018-2022
* All rights reserved, Designed By www.yixiang.co
*/
package co.yixiang.utils;
import cn.hutool.json.JSONObject;
import co.yixiang.domain.ExUser;
import co.yixiang.exception.BadRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* 获取当前登录的用户
* @author Zheng Jie
* @date 2019-01-17
*/
public class SecurityUtils {
public static UserDetails getUserDetails() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
}
if(authentication.getPrincipal() instanceof ExUser){
return (UserDetails) authentication.getPrincipal();
}
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class);
return userDetailsService.loadUserByUsername(userDetails.getUsername());
}
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");
}
/**
* 获取系统用户名称
* @return 系统用户名称
*/
public static String getUsername(){
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
}
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
/**
* 获取系统用户id
* @return 系统用户id
*/
public static Long getUserId(){
Object obj = getUserDetails();
JSONObject json = new JSONObject(obj);
return json.get("id", Long.class);
}
}