Springboot 依赖 token 管理会话(解决单点登录)

最近发生了些糟糕的事情,自己受到了比较大的影响,也影响了更新的频率,后面会慢慢补上。

一般 Springboot 项目默认都会使用 session 的方式管理会话,但是在集群项目中,使用 session 的管理方式就会变的比较麻烦了(单点登录问题),可能需要为每个节点同步 session,还伴随有内存的损耗。这个时候 token 的方式就是一个很好的解决方案,具体原因可以参考之前的《cookie,session,token 的理解》一文。

接下来的内容将会介绍如何在 Springboot 的项目中接入 token 来管理会话。

1、添加依赖库

在这里插入图片描述
标记处的依赖如下

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

2、配置 token

在这里插入图片描述
标记处的配置信息如下

# header:凭证(校验的变量名)
config.jwt.header=token
# expire:有效期1天(单位:s)
config.jwt.expire=3600
# secret:秘钥(普通字符串)
config.jwt.secret=aHR0cHM6Ly9teS5vc2NoaW5hLm5ldC91LzM2ODE4Njg=

3、代码实现

在配置 token 的信息时,会发现没有自动提示,这里需要将配置信息手动的引入代码中。
在这里插入图片描述
JwtConfig 类的具体实现如下

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {
    /*
     * 生成 Token
     */
    public String getToken (String identityId){
        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(identityId)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    /*
     * 获取 Token 中注册信息
     */
    public Claims getTokenClaim (String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    /*
     * Token 是否过期验证
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }

    // 密钥
    private String secret;
    // 超时时间
    private long expire;
    private String header;
}

设置拦截器,拦截 http 请求,校验 token 在这里插入图片描述

import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
    @Resource
    private JwtConfig jwtConfig ;
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 过滤登录的 url
        String uri = request.getRequestURI();
        System.out.println("uri=" + uri);
        if (uri.contains("/login")){
            return true ;
        }
        // token 校验
        String token = request.getHeader(jwtConfig.getHeader());
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtConfig.getHeader());
        }
        if(StringUtils.isEmpty(token)){
            throw new Exception(jwtConfig.getHeader()+ "不能为空");
        }
        Claims claims = jwtConfig.getTokenClaim(token);
        if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
            throw new Exception(jwtConfig.getHeader() + "失效,请重新登录");
        }
        
        request.setAttribute("identityId", claims.getSubject());
        return true;
    }
}

接下来需要让拦截器生效
在这里插入图片描述

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private TokenInterceptor tokenInterceptor ;
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
}

登录的接口
在这里插入图片描述
TokenController 类的实现

import com.alibaba.fastjson.JSON;
import com.hosh.tech.security.JwtConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
public class TokenController {

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class User {
        String username;
        String password;
    }

    @Resource
    private JwtConfig jwtConfig ;
    // 拦截器直接放行,返回Token
    @PostMapping("/login")
    public Map<String,String> login (@RequestBody User user){

        System.out.println("xx------------- 1");

        System.out.println("xx------------- 1 " + JSON.toJSONString(user));
        // 这里需要验证用户名和密码,验证成功以后,走后续的 token 生成流程
        Map<String,String> result = new HashMap<>() ;
        // 省略数据源校验
        String token = jwtConfig.getToken(user.getUsername()+user.getPassword()) ;
        if (!StringUtils.isEmpty(token)) {
            result.put("token",token) ;
        }
        result.put("userName",user.getUsername()) ;
        return result ;
    }
}

4、测试 token 的效果

为了更好的体现 token 对单点登录问题的解决效果,需要做一个集群,集群使用 nginx 做负载均衡。
在这里插入图片描述
在这里插入图片描述
通过截图可以发现,使用 nginx 做负载均衡,同时为这两个实例使用相同的负载均衡策略。

测试接口的实现
在这里插入图片描述
先使用登录接口
在这里插入图片描述
将登录接口返回的 token 作为 header 添加至后续的请求接口
在这里插入图片描述
因为使用了 nginx 做负载均衡,会自动的分发到两个服务实例上,看看连续发送查询请求,两个服务实例的反应,为了区分,两个实例的打印稍微有点区别
在这里插入图片描述
在这里插入图片描述
好了,完美解决单点登录问题。

发布了34 篇原创文章 · 获赞 34 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_19154605/article/details/104955132