登录拦截
因为项目中涉及到这个,自己也接触了,所以记录一下。
认证过程
登录:
1.第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交到服务器的登录处理的Controller层。
QueryWrapper<Person> queryWrapper = new QueryWrapper();
queryWrapper.eq("data_status", GlobalConstant.DATA_STATUS_VALID); queryWrapper.eq("phone",param.get("username")).eq("password",MD5Util.getMD5(param.get("password").toString())).or(i ->i.eq("email",param.get("username")).eq("password",MD5Util.getMD5(param.get("password").toString())).eq("data_status", GlobalConstant.DATA_STATUS_VALID));
Person one = personService.getOne(queryWrapper);
2.Controller调用认证服务进行用户名密码认证,如果认证通过,Controller层调用用户信息服务获取用户信息;
System.out.println("登录成功!");
String id = one.getUuid();//id 当前用户ID
3.返回用户信息后,Controller从配置文件中获取Token签名生成的秘钥信息,进行Token的生成;
//issuer 该JWT的签发者,是否使用是可选的
String issuer = "";
//subject 该JWT所面向的用户,是否使用是可选的
String subject = "";
//ttlMillis 什么时候过期,这里是一个Unix时间戳,是否使用是可选的
long ttlMillis = 1000 * 60 * 30;
//audience 接收该JWT的一方,是否使用是可选的
String audience = "";
String token = TokenUtil.createJWT(id,issuer,subject,ttlMillis,audience);
public static String createJWT(String id,String issuer,String subject,long ttlMillis, String audience){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(APP_KEY);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder jwtBuilder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm,signingKey);
//设置Token的过期时间
if(ttlMillis >0){
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
4.生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据;
String token = TokenUtil.createJWT(id,issuer,subject,ttlMillis,audience);
5.完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程;
HashMap<String,String> result = new HashMap<>();
result.put("userId", id);
result.put("token", token);
return ResultFactory.success(result);
请求认证:
基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;
- 客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
- 认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
- 如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
- 完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
- 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
- 如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;
相关类:
登录Controller类
@RequestMapping("/login")
public Result login(@RequestBody Map<String, Object> param) {
QueryWrapper<Person> queryWrapper = new QueryWrapper();
queryWrapper.eq("data_status", GlobalConstant.DATA_STATUS_VALID);
queryWrapper.eq("phone",param.get("username")).eq("password",MD5Util.getMD5(param.get("password").toString())).or(i ->i.eq("email",param.get("username")).eq("password",MD5Util.getMD5(param.get("password").toString())).eq("data_status", GlobalConstant.DATA_STATUS_VALID));
Person one = personService.getOne(queryWrapper);
if(one !=null) {
System.out.println("登录成功!");
//id 当前用户ID
String id = one.getUuid();
//issuer 该JWT的签发者,是否使用是可选的
String issuer = "";
//subject 该JWT所面向的用户,是否使用是可选的
String subject = "";
//ttlMillis 什么时候过期,这里是一个Unix时间戳,是否使用是可选的
long ttlMillis = 1000 * 60 * 30;
//audience 接收该JWT的一方,是否使用是可选的
String audience = "";
String token = TokenUtil.createJWT(id,issuer,subject,ttlMillis,audience);
HashMap<String,String> result = new HashMap<>();
result.put("userId", id);
result.put("token", token);
return ResultFactory.success(result);
}
System.out.println("登录失败!");
return ResultFactory.fail(null);
}
TokenInterceptor拦截器:
@Component
public class TokenInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取token
String tokenEnmu= request.getHeader("authorization");
if(!CommonUtils.isEmpty(tokenEnmu)) {
try {
Claims claims = TokenUtil.getClaims(tokenEnmu);
String personId = claims.getId();
Date dateExpiration = claims.getExpiration();
if(!CommonUtils.isObjEmpty(dateExpiration)) {
Person person = new Person();
person.setUuid(personId);
//threadlocal保存用户id,用来更新操作记录
ThreadLocalUtil.setResources(person);
//更新时间戳,用来生成token
long ttlMillis = 1000 * 60 * 30;
String newtoken = TokenUtil.createJWT(personId,claims.getIssuer(),claims.getSubject(),ttlMillis,claims.getAudience());
//将token设置到浏览器
response.setHeader("authorization", newtoken);
}else {
response.setStatus(401);
}
} catch (Exception e) {
response.setStatus(401);
}
}else {
response.setStatus(401);
}
//返回 false 则请求中断
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// log.info("postHandle:请求后调用");
// 获取塞入的用户id
// Person personInfo = ThreadLocalUtil.getResources();
// System.out.println("用户id:"+personInfo.getUuid()+"-----------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// log.info("afterCompletion:请求调用完成后回调方法,即在视图渲染完成后回调");
}
}
自定义拦截器:
@Configuration
public class TokenConfig extends WebMvcConfigurationSupport{
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPattern s("/api/login", "/api/file/upload","/api/file/ioimage", "/api/kafka/send/*", "/api/basedata", "/api/report/**");
// super.addInterceptors(registry);
}
}
TokenUtil工具类:
public class TokenUtil {
private static final String APP_KEY = "user_key"; //进行数字签名的私钥
/**
* 一个JWT实际上就是一个字符串,它由三部分组成,头部(Header)、载荷(Payload)与签名(Signature)
* @param id 当前用户ID
* @param issuer 该JWT的签发者,是否使用是可选的
* @param subject 该JWT所面向的用户,是否使用是可选的
* @param ttlMillis 什么时候过期,这里是一个Unix时间戳,是否使用是可选的
* @param audience 接收该JWT的一方,是否使用是可选的
* @return
*/
public static String createJWT(String id,String issuer,String subject,long ttlMillis, String audience){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(APP_KEY);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder jwtBuilder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm,signingKey);
//设置Token的过期时间
if(ttlMillis >0){
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
//私钥解密token信息
public static Claims getClaims(String jwt) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(APP_KEY))
.parseClaimsJws(jwt).getBody();
}