springboot+jwt 实现登录验证

jwt 简介 json web token ,简要说明:前端传验证信息到后端,后端验证通过,返回一个对象,只不过这个对象是被加密的,这样后端就可以为无状态的,每次请求的时候,请求头带上token ,里面封装了对象的信息,我们只需要用拦截器进行拦截,解析token,后端就可以知道是谁登录了界面,可以设置相应的超时时间,超时时间不应太长或者太短,根据实际情况而定,超时用户就需要从新登录

优点: 减少服务器的压力,不用向以前一样将对象保存在session里面,或者是利用redis保存信息,因为redis也是要占用服务器的内存的。非常适合springcloud 微服务。

jwt 分为三部分

1.头部 header

{
  "alg": "HS256",
  "typ": "JWT"
}

alg 表示签名算法,常用HS256 当然也有很多的支持的签名算法 具体支持可以参考jwt官网-》》进入官网鼠标往下一直挪,你就能看到了。https://jwt.io/

typ: token令牌的类型 jwt 统一写JWT

 

2.payload 负载

也是一个json 的对象,下面是官方字段:

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号  

当然你也可以定义一些其他的字段

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

但是由于jwt 默认是不加密的,所以不要保存敏感信息 

3.Signature 签名

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

说了折磨多,应该见识一下jwt 生成的token的真面目了

不同的颜色代表不同的部分。

好了不多说,上代码:

springboot 集成jwt 实验token验证

先引入依赖:

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

新建一个实体类 用来保存我们的对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /*user id*/
    private String id;

    private String username;

    private String password;

}

 我们 这里使用lombok 自动生成get set 方法 @AllArgsConstructor 生成包含所有参数的构造方法
@NoArgsConstructor 生成无参的构造方法 还可以有@getter @setter 你们懂得!别跑偏了 回到正题,好的对象我们已经建立好了

我们这里由于需要进行是否有token 的验证,比如登录不需要验证 ,登录以后需要访问的页面需要访问需要验证,所以定义两个注解,有人会想,使用aop不就行了,当然可以,但是在面对众多的接口的时候,aop进行管理是否会有一些不妥,我们这里使用自己定义的注解,需要进行验证的时候只需要添加注释,定义一个拦截器,在所有url访问的时候先看看是否有注解,有注解就进行验证是不是更加灵活呢!

开始 自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

这个注解的作用是用来pass掉token 的 也就是不需要token验证的需求

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

这个注解时候用来使用token 的,有的人也许就有点蒙了,可能没有接触过自定义注解,简单来介绍一下

用几个常见的注解源码来给你们介绍一下

 

熟不熟悉,对我就是抄的,哈哈,具体的意义可以开一下相关文档

@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:说明子类可以继承父类中的该注解

这样你是不是也可以自己定义注解了呢

好的我们继续

定义好了注解,就写我们的根据对象获取token 的代码了

public class TokenUtil {


    /**
     * 生成token 的方法
     * @param user
     * @return
     */
    public static String  getToken(User user){
        String token = "";
        token = JWT.create().withAudience(user.getId())
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}

 Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。
withAudience()存入需要保存在token的信息,这里我把用户ID存入token

接下来我们写一个拦截器,来拦截相应的请求

public class AuthenticationInterceptor implements HandlerInterceptor {

    @Autowired
    UserService userService;




    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //进入方法之前进行的操作
        //获取token
        String token  =  request.getHeader("token");
        //如果不是映射到方法直接通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        if(method.isAnnotationPresent(PassToken.class))
        {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if(passToken.required()){
                return true;
            }
        }
        String userId = null;
        if(method.isAnnotationPresent(UserLoginToken.class))
        {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if(userLoginToken.required()){
              if(token == null){
                  throw new RuntimeException("无token,请重新登录");
              }
              //获取token的userid
                try{
                    userId = JWT.decode(token).getAudience().get(0);
                }
                catch (JWTDecodeException e){
                    throw new RuntimeException("401");
                }
                User user = userService.getUser(userId);
                if(user==null){
                    throw new RuntimeException("用户不存在");
                }
                //验证token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try{
                    jwtVerifier.verify(token);
                }catch (JWTVerificationException e){
                    throw new RuntimeException("401");
                }

                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //方法处理之后但是并未渲染视图的时候进行的操作
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //渲染视图之后进行的操作
    }
}

method.isAnnotationPresent(PassToken.class) 这里就看到我们的注解的作用了,利用的是java 的反射机制,看是否有这样的注解,有进就行相关的操作

这里我们的token 是在请求头中被传过来的。

主要流程:

1.从 http 请求头中取出 token
2.判断是否映射到方法
3.检查是否有passtoken注释,有则跳过认证
4.检查有没有需要用户登录的注解,有则需要取出并验证
5.认证通过则可以访问,不通过会报相关错误信息

拦截定义完成,需要自己去配置一下springmvc 的拦截器

@Configuration
public class WebMvcConfigration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");

    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor(){
        return new AuthenticationInterceptor();
    }
}

 这里默认是拦截所有的请求

这里突然想到一个坑,有的博客会去用@EnableWebMvc这个注解,当你使用这个注解时候,会取消到springboot 的默认配置,所以请谨慎使用

接下来我们就到了正常的mvc阶段了,我这里我了简单就没有连接数据库,全部使用UserService 进行操作

public interface UserService {
    User getUser(String id);
    User getUserByName(String username);
}

@Service
public class UserServiceImpl implements UserService {

    private List<User> userList = new ArrayList<>();

    public void getUserlist(){
        User user1 = new User("1","liming","liming");
        User user2 = new User("2","limin","liming");
        User user3 = new User("3","limi","liming");
        User user4 = new User("4","lim","liming");
        User user5 = new User("5","li","liming");
        this.userList.add(user1);
        this.userList.add(user2);
        this.userList.add(user3);
        this.userList.add(user4);
        this.userList.add(user5);


    }
    public UserServiceImpl(){
        getUserlist();
    }

    @Override
    public User getUser(String id) {

       User user = null;
        for (User usertest:userList
             ) {
            if(usertest.getId().equals(id))
            {
                user = usertest;
            }

        }
        return user;
    }
    public User getUserByName(String username){

        for (User user:userList
             ) {
            if(user.getUsername().equals(username)){
                return user;
            }
        }
        return null;
    }
}

一个接口,一个实现类,恩恩,熟悉的感觉

进行重要的controller 的编写了

@RequestMapping("/api")
@RestController
public class UserController {

    @Autowired
    UserService userService;

    @PassToken
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public Object login(@Valid User user ,BindingResult bindingResult){
        if(bindingResult.hasErrors()){
           throw new RuntimeException(bindingResult.getFieldError().toString());
        }
       User userforbase =  userService.getUserByName(user.getUsername());

       if(userforbase==null){
           return JSONResultUtils.errorMsg("登录失败,用户不存在");
       }
       if(!userforbase.getPassword().equals(user.getPassword())){
           return JSONResultUtils.errorMsg("登录失败,密码错误");
       }
       String token = TokenUtil.getToken(userforbase);
       return token;
    }
    @UserLoginToken
    @RequestMapping("/getmessage")
    public String getmessage(){
        return JSONResultUtils.ok("你已经通过验证");
    }


}

这里应该有两个方法,login 使用来登录验证的 加上注解 就不需要token 字段

getmessage 使用来模拟我们那些需要token的方法,加上注解后,就需要token,如果没有就会报错

哦哦还有那个jsonResultUtils 是一个返回json的工具类  是我们想前端返回的时候进行的封装,一般包含的json数据是 status 状态

msg 内容   data 数据

接下来使用postman进行测试一下:

先进行一下无token的getmessage

来我们登录一下

好的我们已经获取到了token了

那我我们将token传入再尽心验证

 

 

 

成功!本人大白一个,如果有什么错误还需要你们的指正,当然那也感谢一下大神的帮助:

参考文章

https://www.cnblogs.com/shihaiming/p/9565835.html   

http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

 

 

 

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值