登录拦截有很多种方法,网上代码各式各样,我总结一下我最近用到的一种方法。
流程:用户登录后除了返回用户信息还会返给前端带有userId的一个token值,并将token值存入redis,前端拿着token去调用接口,在请求接口时拦截器会先检测该接口是否有自定义注解去做拦截验证,然后判断请求头中是否带有token请求及redis中token是否过期,然后进行拦截。
不多比比,直接上代码
1、创建自定义注解类
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.RetentionPolicy;
/**
* 在不需要登录验证的Controller的方法上使用此注解
*/
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
@Documented
@Inherited
public @interface LoginRequired {
}
2、创建token工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
public class TokenUtils {
/**
* 签名秘钥
*/
public static final String SECRET = "USER";
/**
* 生成token
* @param id 一般传入userId
* @return
*/
public static String createJwtToken(String id){
long ttlMillis = System.currentTimeMillis();
return createJwtToken(id,ttlMillis);
}
/**
* 生成Token
*
* @param id
* 编号
* @param issuer
* 该JWT的签发者,是否使用是可选的
* @param subject
* 该JWT所面向的用户,是否使用是可选的;
* @param ttlMillis
* 签发时间
* @return token String
*/
public static String createJwtToken(String id,long ttlMillis) {
// 签名算法 ,将对token进行签名
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成签发时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 通过秘钥签名JWT
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
// Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.signWith(signatureAlgorithm, signingKey);
// if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
// Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
// Sample method to validate and read the JWT
public static Claims parseJWT(String jwt) {
// This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
.parseClaimsJws(jwt).getBody();
return claims;
}
}
3、编写拦截器
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import com.zewei.commons.redis.RedisUtil;
public class AuthenticationInterceptor implements HandlerInterceptor {
//Redis根据自己业务代码调用
@Autowired
RedisUtil redisUtil;
private static final Logger LOGGER= LoggerFactory.getLogger(AuthenticationInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
LOGGER.info("没有映射到方法,则无需检查直接通过");
return true;
}
HandlerMethod heHandlerMethod = (HandlerMethod)handler;
Method method = heHandlerMethod.getMethod();
//判断接口是否需要登录
LoginRequired loginRequired=method.getAnnotation(LoginRequired.class);
//有LoginRequired注解,不需要认证
if(loginRequired!=null){
return true;
}
//从header中获取token
String token = request.getHeader("token");
if (token!=null) {
//判断redis中token是否失效
boolean falg = redisUtil.hasKey(token);
if (falg) {
return true;
}
}
//登录状态检查,使用response返回给前端指定信息
setHeader(request, response, "{\r\n" +
" \"code\": \"-1\",\r\n" +
" \"message\": \"请进行登录!\",\r\n" +
" \"data\": \"null\"\r\n" +
"}");
return false;
}
/**
* response设置header,实现跨域
* @param request
* @param response
* @param message 返回给前端的消息,json格式
*/
@SuppressWarnings("unused")
private void setHeader(HttpServletRequest request,HttpServletResponse response,String message){
try {
//跨域的header设置
response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", request.getMethod());
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
//防止乱码,适用于传输JSON数据
response.setHeader("Content-Type","application/json;charset=UTF-8");
response.getWriter().write(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4、注入拦截器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
5、控制层接口
/**
* 用户登录
* 加@LoginRequired注解表示该接口不需要进行拦截
* @return
*/
@CrossOrigin
@ResponseBody
@LoginRequired
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ApiOperation(value = "用户登录")
public ActionResponse<ModelMap> login(String account, String password, ModelMap model) {
try {
User user = userService.selectByUser(account,password);
if (user != null) {
//生成token
String token = TokenUtils.createJwtToken(user.getuID());
//将token存入redis,两小时失效
redisUtil.set(token, token, 7200L);
model.put("token", token);
model.put("user", user);
return new ActionResponse<ModelMap>(ErrorMsg.success.getCode(),
ErrorMsg.success.getMessage(),model);
}
} catch (Exception e) {
e.printStackTrace();
}
return new ActionResponse<ModelMap>(ErrorMsg.error.getCode(), ErrorMsg.error.getMessage());
}
如有不足或改进,欢迎大家指正,技术无界限,互相进步。