一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
六、注册功能
PasswordEncoder#encode对密码进行加密,加密是非对称加密,就是相同的密码加密后的字符串都不一样。
@Override
public Boolean register(UserInfo userInfo) {
List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>()
.eq(UserInfo::getUsername, userInfo.getUsername()));
if (!selectedList.isEmpty()) {
throw new RuntimeException("注册失败,该用户名已存在");
}
// 密码加密
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(userInfo.getPassword());
userInfo.setPassword(encodedPassword);
return save(userInfo);
}
七、登录功能
PasswordEncoder#matches验证密码
@Override
public String login(UserInfo userInfo) {
List<UserInfo> selectedList = list(new LambdaQueryWrapper<UserInfo>()
.eq(UserInfo::getUsername, userInfo.getUsername()));
if (selectedList.isEmpty()) {
throw new RuntimeException("登录失败,账号不存在");
}
UserInfo selected = selectedList.get(0);
String encodedPassword = selected.getPassword();
// 判断密码是否正确
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean result = passwordEncoder.matches(userInfo.getPassword(), encodedPassword);
if (!result) {
throw new RuntimeException("登录失败,用户密码错误");
}
// 生成令牌
HashMap<String, Object> map = new HashMap<>(2);
map.put("userId", selected.getId());
String token = JwtUtil.generateToken(map);
return token;
}
八、JWT的生成与验证工具类
JwtUtil 类
import cn.hutool.core.date.DateUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
/**
* 令牌密码 不少于32位
*/
private static final String SECRET = "token_secret";
/**
* 令牌前缀
*/
private static final String TOKEN_PREFIX = "Bearer";
/**
* 令牌过期时间
*/
private static final Integer EXPIRE_SECONDS = 60 * 60 * 24 * 7;
/**
* 生成令牌
*/
public static String generateToken(Map<String, Object> map) {
String jwt = Jwts.builder()
.setSubject("user info").setClaims(map)
.signWith(SignatureAlgorithm.HS512, SECRET)
.setExpiration(DateUtil.offsetSecond(new Date(), EXPIRE_SECONDS))
.compact();
return TOKEN_PREFIX + "_" + jwt;
}
/**
* 验证令牌
*/
public static Map<String, Object> resolveToken(String token) {
if (token == null) {
throw new RuntimeException("令牌为空");
}
try {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replaceFirst(TOKEN_PREFIX + "_", ""))
.getBody();
} catch (ExpiredJwtException e) {
throw new RuntimeException("令牌已过期");
} catch (Exception e) {
throw new RuntimeException("令牌解析异常");
}
}
}
九、统一请求拦截
拦截所有的请求进入拦截器,从请求头获取令牌,解析令牌,并保存用户ID到上下文对象中
TokenInterceptor 类 令牌拦截器
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class TokenInterceptor implements HandlerInterceptor {
/**
* 请求头
*/
private static final String HEADER_AUTH = "Authorization";
/**
* 安全的url,不需要令牌
*/
private static final List<String> SAFE_URL_LIST = Arrays.asList("/userInfo/login", "/userInfo/register");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.setContentType("application/json; charset=utf-8");
String url = request.getRequestURI().substring(request.getContextPath().length());
System.out.println(url);
// 登录和注册等请求不需要令牌
if (SAFE_URL_LIST.contains(url)) {
return true;
}
// 从请求头里面读取token
String token = request.getHeader(HEADER_AUTH);
if (token == null) {
throw new RuntimeException("请求失败,令牌为空");
}
// 解析令牌
Map<String, Object> map = JwtUtil.resolveToken(token);
Long userId = Long.parseLong(map.get("userId").toString());
ContextHolder.setUserId(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ContextHolder.shutdown();
}
}
- 所有的请求都通过
preHandle方法 ContextHolder.setUserId(userId);请求开始将解析的用户id放入上下文对象。ContextHolder.shutdown();请求结束从上下文对象中剔除用户id。
然后再将WebMvcConfiguration 类 添加拦截器到MVC配置中
ContextHolder 类 上下文对象类
public class ContextHolder {
public static ThreadLocal<Long> context = new ThreadLocal<>();
public static void setUserId(Long userId) {
context.set(userId);
}
public static Long getUserId() {
return context.get();
}
public static void shutdown() {
context.remove();
}
}
- 主要用到了ThreadLocal,就是在一个请求线程中都可以获取到上下文对象。
- 如修改密码
Long userId = ContextHolder.getUserId();获取用户id。 - 修改密码根据用户id去更新数据,用户id直接从上下文对象中拿,这样就不用从前端传过来,如果从前端传过来,相当于任何人都能修改其它人的密码了,非常不安全。
- 从上下文中拿,也就是从令牌中拿,对接口就行了保护,只能自己操作自己的数据。