验证登录
登录的校验对比加盐的密钥是否匹配即可,正常项目中尽量不要明文发送密码。
首先简单确认是否登录成功:
@PostMapping("/login")
public Map<String, Object> login(@RequestBody Map<String, Object> params) throws Exception {
Map<String, Object> map = ETools.responseMap();
String username = params.get("username").toString();
String password = params.get("password").toString();
List<EUser> list = mapper.getUserBy(username);
if (list.size() > 0) {
EUser user = list.get(0);
if (ETools.verifyMd5(password, salt, user.getPassword())) {
return map;
}
}
map.put("status", 0);
map.put("msg", "login failure");
return map;
}
/**
* MD5验证方法
*
* @param text 明文
* @param key 密钥
* @param md5 密文
* @return true/false
* @throws Exception
*/
public static boolean verifyMd5(String text, String key, String md5) throws Exception {
//根据传入的密钥进行验证
String md5Text = md5(text, key);
if(md5Text.equalsIgnoreCase(md5))
{
System.out.println("MD5验证通过");
return true;
}
return false;
}
生成token
添加JWT依赖(json web token)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
</dependency>
使用JWT生成token
public String getToken(EUser user) {
String token = JWT.create()
.withAudience(String.valueOf(user.getId()))
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
使用JWT验证token
public EUser getUser(String token) {
long uid;
try {
uid = Long.parseLong(JWT.decode(token).getAudience().get(0).toString());
} catch (JWTDecodeException e) {
throw new RuntimeException("401");
}
EUser user = mapper.getOne(uid).get(0);
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return user;
}
添加运行时可识别的注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredToken {
boolean required() default true;
}
@Target:注解的作用目标
@Target(ElementType.TYPE) 接口、类、枚举、注解
@Target(ElementType.FIELD) 字段、枚举的常量
@Target(ElementType.METHOD) 方法
@Target(ElementType.PARAMETER) 方法参数
@Target(ElementType.CONSTRUCTOR) 构造函数
@Target(ElementType.LOCAL_VARIABLE) 局部变量
@Target(ElementType.ANNOTATION_TYPE) 注解
@Target(ElementType.PACKAGE) 包
@Retention:注解的保留位置
RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
拦截请求
增加接口添加上面自定义的注解@RequiredToken表示该接口需要登录
@RequiredToken
@RequestMapping("/getMessage")
public Map<String, Object> getMessage(@RequestBody Map<String, Object> params) throws Exception {
Map<String, Object> map = ETools.responseMap();
map.put("msg", "return message string");
return map;
}
新建一个配置类,通过@Configuration注释告诉Spring将此类作为配置类bean添加到IOC容器中,spring2.0需要实现WebMvcConfigurer的方式重写addInterceptors,在方法里返回一个合适的配置类的实例用作配置。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
新建一个配置类AuthenticationInterceptor实现HandlerInterceptor接口。preHandle方法会在请求处理前进行拦截,在此方法中调用method类的isAnnotationPresent检查对应的注解是否是自定义注解,并在method的getAnnotation方法中获取注解类的方法完成自定义的逻辑判断结果。使用上面getUser方法进行token有效性校验。
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private EUserMapper mapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (!(handler instanceof HandlerMethod)) {
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
//skip pass token type
if (method.isAnnotationPresent(PassToken.class)) {
if (method.getAnnotation(PassToken.class).required()) {
return true;
}
}
//check the validity of the token
if (method.isAnnotationPresent(RequiredToken.class)) {
if (!method.getAnnotation(RequiredToken.class).required()) {
return true;
}
if (token == null) {
throw new RuntimeException("请先登录");
}
EUser user = getUser(token);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
public EUser getUser(String token) {
long uid;
try {
uid = Long.parseLong(JWT.decode(token).getAudience().get(0).toString());
} catch (JWTDecodeException e) {
throw new RuntimeException("401");
}
EUser user = mapper.getOne(uid).get(0);
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return user;
}
}
运行项目,使用postman进行检查,先在登录接口中增加token的返回:
@PostMapping("/login")
public Map<String, Object> login(@RequestBody Map<String, Object> params) throws Exception {
Map<String, Object> map = ETools.responseMap();
String username = params.get("username").toString();
String password = params.get("password").toString();
List<EUser> list = mapper.getUserBy(username);
if (list.size() > 0) {
EUser user = list.get(0);
if (ETools.verifyMd5(password, salt, user.getPassword())) {
String token = getToken(user);
Map<String, String> data = new HashMap<String, String>();
data.put("token", token);
map.put("data", data);
return map;
}
}
map.put("status", 0);
map.put("msg", "login failure");
return map;
}
调用登录接口获取token:
调用getMessage接口进行测试:
使用错误的token返回:
不使用token返回:
使用正确token返回: