SpringBoot+JWT 实现接口登录验证

 一、介绍

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

1、头部(Header)

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

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

在头部指明了签名算法是HS256算法。 我们进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2、载荷(playload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

(1)标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token。

(2)公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

(3)私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

定义一个payload:

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

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

3、签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)

payload (base64后的)

secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

二、实践(简单实现调用后台接口需要token验证)

1、引入依赖

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

2、自定义注解

/**
 * @author yang
 * @date 2021年11月19日 11:09
 * 这个注解的作用是用来pass掉token 的 也就是不需要token验证的需求   
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
/**
 * @author yang
 * @date 2021年11月19日 11:10
 * 这个注解时候用来使用token 的
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

3、创建获取Token的工具类

/**
 * @author yang
 * @date 2021年11月19日 11:12
 */
public class TokenUtil {
    /**
     * 生成token 的方法
     * @param user
     * @return
     */
    public static String  getToken(SysUser user){

        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + 24*60*60*1000);
        String token = "";
        token = JWT.create().withAudience(user.getUserId().toString())
                .withExpiresAt(expireDate)
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}

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

4、创建拦截器,来拦截相应的请求

/**
 * @author yang
 * @date 2021年11月19日 11:19
 */
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Autowired
    SysUserService 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("token已过期,请重新请求");
                }
                SysUser user = userService.getUser(Long.parseLong(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("token已过期,请重新请求");
                }

                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.认证通过则可以访问,不通过会报相关错误信息
 

5、配置springmvc 的拦截器

/**
 * @author yang
 * @date 2021年11月19日 11:22
 */
@Configuration
public class WebMvcConfigration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/rest/**");

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

 这里我拦截的是/rest/下的所以请求,大家根据自己实际的请求路径进行修改。这里突然想到一个坑,有的博客会去用@EnableWebMvc这个注解,当你使用这个注解时候,会取消到springboot 的默认配置,所以请谨慎使用。

6、mvc阶段(主要是Controller,其他就不介绍了)

/**
 * @author yang
 * @date 2021年11月19日 11:27
 */
@RequestMapping("/rest")
@RestController
public class UserController extends BaseController {

    @Autowired
    private SysUserService sysUserService;

    @PassToken
    @PostMapping(value = "/login2")
    public Object login(SysUser sysUser ){

        //用户登录
        BooleanResult booleanResult = sysUserService.login(sysUser.getLoginName(),sysUser.getPassword());

        SysUser user = (SysUser) booleanResult.getObject();

        String token = TokenUtil.getToken(user);
        return token;
    }

    @UserLoginToken
    @RequestMapping("/getmessage")
    public String getmessage(){
        return "你已经通过验证";
    }


}

这里应该有两个方法,login 使用来登录验证的 加上注解 就不需要token 字段,getmessage 使用来模拟我们那些需要token的方法,加上注解后,就需要token

三、测试

1、我们先使用Postman调下/rest/getmessage接口,此时无token

2、接着调用/rest/login2接口生成token 

3、再次调/rest/getmessage,这次在请求的Headers中带入刚生成的token

 好,结束,收工,最后附上工程目录及请求图示

参考: springboot+jwt 实现登录验证_大白的博客-CSDN博客_jwt登录认证

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单点登录(Single Sign-On,简称SSO)是一种身份验证技术,可以让用户只需一次登录,就可以访问多个应用程序。在实际开发中,我们可以使用Spring Boot、JWT和Redis来实现单点登录功能。 下面是实现单点登录的步骤: 1. 创建Spring Boot项目并引入所需依赖:spring-boot-starter-web、spring-boot-starter-data-redis和jjwt。 2. 创建一个User实体类,包含用户名和密码等信息。 3. 创建一个UserService,实现对用户信息的操作,包括注册、登录等。 4. 引入JWT依赖后,我们需要创建一个JWTUtil类,实现token的生成和解析。 5. 创建一个LoginController,用于处理用户的登录请求。在登录成功后,生成token并将其存储到Redis中。 6. 创建一个AuthController,用于验证用户的token是否有效。在验证成功后,可以获取用户信息并返回。 7. 在需要进行单点登录验证的应用程序中,只需要在请求中携带token,并调用AuthController进行验证即可。 具体实现细节可以参考以下代码示例: User实体类: ```java public class User { private String username; private String password; // 省略setter和getter方法 } ``` UserService接口: ```java public interface UserService { void register(User user); String login(String username, String password); } ``` UserService实现类: ```java @Service public class UserServiceImpl implements UserService { @Autowired private RedisTemplate<String, String> redisTemplate; @Override public void register(User user) { // 省略用户注册逻辑 } @Override public String login(String username, String password) { // 省略用户登录逻辑 // 登录成功后生成token并存储到Redis中 String token = JWTUtil.generateToken(username); redisTemplate.opsForValue().set(username, token, 30, TimeUnit.MINUTES); return token; } } ``` JWTUtil类: ```java public class JWTUtil { private static final String SECRET_KEY = "my_secret_key"; private static final long EXPIRATION_TIME = 3600000; public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } } ``` LoginController: ```java @RestController public class LoginController { @Autowired private UserService userService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody User user) { String token = userService.login(user.getUsername(), user.getPassword()); return ResponseEntity.ok(token); } } ``` AuthController: ```java @RestController public class AuthController { @Autowired private RedisTemplate<String, String> redisTemplate; @GetMapping("/auth") public ResponseEntity<User> auth(@RequestHeader("Authorization") String token) { String username = JWTUtil.getUsernameFromToken(token); if (StringUtils.isEmpty(username)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } String redisToken = redisTemplate.opsForValue().get(username); if (!token.equals(redisToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } User user = new User(); user.setUsername(username); return ResponseEntity.ok(user); } } ``` 在请求中携带token的示例: ```java @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> { String token = // 从Redis中获取token request.getHeaders().add("Authorization", token); return execution.execute(request, body); })); return restTemplate; } } ``` 以上就是使用Spring Boot、JWT和Redis实现单点登录的步骤和示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值