项目经验-用户注册-登录
注册流程
用户点击注册,系统将密码加密,然后保存到数据库。(这里没有校验用户重复情况)

登录流程
用户登录,校验密码,生成 Token。

校验流程
通过拦截器校验每次用户访问的 Token。

扩展思考
其实这里也可以不使用 Shiro,而只是使用 SpringBoot 自身的拦截器去实现。
定义工具类:SecurityUtils
/**
* SecurityUtils 工具类
* 获取 Token 和密码加密
*/
public class SecurityUtils {
/**
* 获取请求token
*/
public static String getToken() throws IllegalStateException {
return getToken(Objects.requireNonNull(ServletUtils.getRequest()));
}
/**
* 根据request获取请求token
*/
public static String getToken(HttpServletRequest request) throws IllegalStateException{
// 从 Hearder 中获取
String header = request.getHeader(TokenConstants.AUTHENTICATION);
// 从 Parameter 中获取
if (StringUtils.isBlank(header)) {
header = request.getParameter(TokenConstants.AUTHENTICATION);
}
// 从 Cookie 中获取
if (StringUtils.isBlank(header)) {
Cookie[] cookies = request.getCookies();
if (ObjectUtils.isEmpty(cookies)) {
throw new IllegalStateException("Request Token Is Empty");
}
for (Cookie cookie : cookies) {
if (Objects.equals(cookie.getName(), TokenConstants.AUTHENTICATION)) {
header = cookie.getValue();
}
}
}
// 都为空的情况
if (StringUtils.isBlank(header)) {
throw new IllegalStateException("Request Token Is Empty");
}
if (!header.startsWith(TokenConstants.PREFIX)) {
throw new IllegalStateException("Request Token Is Empty");
}
return JwtUtils.getUserKey(header.substring(TokenConstants.PREFIX.length()));
}
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
定义工具类:JwtUtils
/**
* Jwt工具类
*/
public class JwtUtils {
public static String secret = TokenConstants.SECRET;
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
public static String createToken(Map<String, Object> claims)
{
return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
public static Claims parseToken(String token)
{
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 根据令牌获取用户标识
*
* @param token 令牌
* @return 用户ID
*/
public static String getUserKey(String token)
{
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.USER_KEY);
}
/**
* 根据令牌获取用户标识
*
* @param claims 身份信息
* @return 用户ID
*/
public static String getUserKey(Claims claims)
{
return getValue(claims, SecurityConstants.USER_KEY);
}
/**
* 根据令牌获取用户ID
*
* @param token 令牌
* @return 用户ID
*/
public static String getUserId(String token)
{
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.USER_ID);
}
/**
* 根据身份信息获取用户ID
*
* @param claims 身份信息
* @return 用户ID
*/
public static String getUserId(Claims claims)
{
return getValue(claims, SecurityConstants.USER_ID);
}
/**
* 根据令牌获取用户名
*
* @param token 令牌
* @return 用户名
*/
public static String getUserName(String token)
{
Claims claims = parseToken(token);
return getValue(claims, SecurityConstants.USER_PHONE);
}
/**
* 根据身份信息获取用户名
*
* @param claims 身份信息
* @return 用户名
*/
public static String getUserName(Claims claims)
{
return getValue(claims, SecurityConstants.USER_PHONE);
}
/**
* 根据身份信息获取键值
*
* @param claims 身份信息
* @param key 键
* @return 值
*/
public static String getValue(Claims claims, String key)
{
return Convert.toStr(claims.get(key), "");
}
}
定义拦截器:LoginInterceptor
/**
* 接口拦截器
*/
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private RedisUtils redisUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IllegalStateException {
// 先从缓存中获取数据
String token = SecurityUtils.getToken(request);
String key = RedisConstants.LOGIN_TOKEN_KEY + token;
LoginUser loginUser = (LoginUser) redisUtils.get(key);
if (ObjectUtils.isNotEmpty(loginUser)) {
LoginUserUtils.set(loginUser);
return true;
} else {
throw new IllegalStateException("User Token Is Expires");
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
LoginUserUtils.remove();
}
}
启用拦截器:LoginConfig
/**
* 启用拦截器
*/
@Configuration
public class LoginConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/api/**")
.excludePathPatterns("/api/user/login");
}
/**
* 显示 swagger-ui.html文档展示页,还必须注入 swagger 资源
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
注册登录流程

验证校验流程

业务请求流程

- 参考