分布式鉴权之JWT整合shiro

关于Jwt

Jwt是什么

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

Jwt结构

Header: 由典型的两部分组成->token类型(JWT)和算法名称(HMAC/SHA256/RSA/等等),一般情况下Header是不变的
Payload: 声明部分一般包含过期时间,用户名密码等等信息具体可以根据Jwt生成Utils类自定义,如下

public String generateJwt(String username, String password){
        if (StringUtils.isBlank(username)||StringUtils.isBlank(password)){
            return null;
        }
        String token=Jwts.builder().setSubject(subject)
                .claim("un",username)
                .claim("pwd",password)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()+expire))
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
        return token;
    }

Signature: 签名部分的生成依赖于前面两部分和秘钥,

Jwt应用

1、分布式场景下的鉴权或者Oauth
2、用作信息交换

Jwt整合Shiro

自定义Token-JwtToken

自定义token实在AuthenticationToken基础上写的一个shiro令牌

public class JwtToken implements AuthenticationToken, Serializable {

    private static final long serialVersionUID = 1L;
    private String token;

    public JwtToken(String token) {
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public String toString() {
        return "JwtToken{" +
                "token='" + token + '\'' +
                '}';
    }

ShiroConfig配置类

最常见不过的配置类,其中自定义了两个filters,在进行Jwt进行过滤的时候
难免会出现路径已经过滤的情况,这时候我这边出现了个问题是只有第一个Filter生效,后面的不起作用,将在文章末为大家提供详细解决方案

 @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager manager/*, RolesFilter rolesFilter*/){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        // 必须设置SecurityManager
        bean.setSecurityManager(manager);
        // 未登录,是否执行是根据subject.login判断的
        bean.setLoginUrl("/api/v1/gate/user/pub/needLogin");
        bean.setSuccessUrl("/");
        // 无权限
        bean.setUnauthorizedUrl("/api/v1/gate/user/pub/notPermit");
        // 设置自定义filter
        Map<String,Filter> filters=new TreeMap<>();
        filters.put("jwt",new JwtFilter());
        filters.put("roles",new RolesFilter());
        bean.setFilters(filters);
        // 路径拦截顺序,一定要使用LinkedHashMap,否则时而拦截,时而不拦截
        Map<String,String> map=new LinkedHashMap<>();

        // 登出
        map.put("/api/v1/gate/user/logout","logout");
        // 游客模式
        map.put("/api/v1/gate/user/pub/**","anon");
        map.put("/a/b","jwt");
        // 登录才可以
        map.put("/api/v1/gate/user/authc/**","authc");
        // 需要admin用户权限才可以访问
        map.put("/api/v1/gate/user/admin/**","roles[root]");
        map.put("/api/v1/gate/user/goods/video/update","perms[video_update]");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }


    /**
     * 认证管理
     * @return
     */
    @Bean
    public SecurityManager getSecurityManager(){
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(getJwtRealm());
        return manager;
    }


    /**
     * 自定义Realm
     * @return
     */
    @Bean
    public JwtRealm getJwtRealm(){
        JwtRealm jwtRealm=new JwtRealm();
        return jwtRealm;
    }

JwtFilter

自定义的Jwt拦截器,目的在于拦截请求,看请求头中是否包含“token”

  @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (((HttpServletRequest) request).getHeader(DEFAULT_JWT_HEADER) != null) {
            log.info("接口请求头携带token");
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            log.info("接口请求头未携带token");
            sendJsonMessage((HttpServletResponse) response,JsonData.buildError(-1,"未发现token,请登录"));
        }
        return false;
    }


    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response)  {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(DEFAULT_JWT_HEADER);
        JwtToken jwtToken = new JwtToken(token);
        getSubject(request, response).login(jwtToken);
        return true;
    }

JwtRealm

为登录认证于鉴权提供保证

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) {
        log.info("执行认证...");
        // 强转成自定义的JwtToken
        JwtToken jwt=(JwtToken)authToken;
        // 获取token
        String token=(String)jwt.getCredentials();
        // 先检查能否取出用户
        String username=jwtConfig.getUsername(token);
        if(username==null){
            throw new AuthenticationException("token无效");
        }
        // 查找数据库,看是否存有当前用户
        User user=userService.selectUserByUsername(username);
        if(user==null){
            throw new AuthenticationException("用户不存在") ;
        }
        if(!jwtConfig.verify(token,user.getPassword())){
            throw new AuthenticationException("账户密码错误!");
        }
        return new SimpleAuthenticationInfo(jwt,token,getName());
    }


    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("执行授权...");
        JwtToken jwtToken= (JwtToken) principals.getPrimaryPrincipal();
        User user=userService.selectUserByUsername(jwtConfig.getUsername(jwtToken.getToken()));
        // 角色集合
        List<String> strRoleList=new ArrayList<>();
        // 权限集合
        List<String> strPerList=new ArrayList<>();
        // 获取用户的角色集合
        List<Role> roles=user.getRoles();
        // 遍历形成角色权限集合
        for (Role role : roles) {
            strRoleList.add(role.getName());
            for (Permission permission : role.getPermissions()) {
                if (permission!=null){
                    strPerList.add(permission.getName());
                }
            }
        }
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        info.addRoles(strRoleList);
        info.addStringPermissions(strPerList);
        return info;
    }

问题解决

问题验证

本人猜想是因为路径拦截的问题(前面的拦截器将路径全部拦截了,后面才不生效),但是苦于找不到源码切入点,于是做了一个试验在ShrioConfig中使用JwtFilter对/a/b路径进行拦截
如下图:
在这里插入图片描述
已经拦截成功说明拦截器生效。
因为getSubject(request, response).login(jwtToken);的执行由原来的Controller层迁移到了JwtFilter中所以单独复制了一个生成token的接口目的在于对当前用户执行.login(token)操作,如下图:login2()方法

 /**
     * 登录接口
     * @return JsonData
     */
    @PostMapping("token")
    public JsonData login(@RequestBody UserQuery userQuery) {
        String token= jwtConfig.generateJwt(userQuery.getName(),userQuery.getPwd());
        if(token==null){
            return JsonData.buildError(-1,"用户名或密码不能为空");
        }
        return JsonData.buildSuccess(1, token,"操作成功");
    }

    @PostMapping("token2")
    public JsonData login2(@RequestBody UserQuery userQuery) {
        Subject subject= SecurityUtils.getSubject();
        String token= jwtConfig.generateJwt(userQuery.getName(),userQuery.getPwd());
        if(token==null){
            return JsonData.buildError(-1,"用户名或密码不能为空");
        }
        // 即将进入doGetAuthenticationInfo
        subject.login(new JwtToken(token));
        return JsonData.buildSuccess(1, token,"登录成功");
    }

访问login2对应接口,成功返回token,如下图:
在这里插入图片描述
接着再次访问需要roles[root]角色的接口,如下图:
在这里插入图片描述
从图中可以看出RolesFilter生效

解决方案

在ShiroConfig中手动加入

 @Bean
    public RolesFilter getRolesFilter(){
        return new RolesFilter();
    }

并进行如下更改
在这里插入图片描述
问题解决,,, 哈哈哈,超级开心

另附

解决思路是在偶尔间看到的一篇博文,地址如下

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Shiro分布式是指在分布式系统中使用Shiro进行身份验证和授的过程。对于这个问题,可以参考引用和引用中提供的相关文档和示例代码来实现Shiro分布式的功能。首先,可以查看jsets-shiro-spring-boot-starter项目详情以了解如何在Spring Boot项目中集成Shiro。并且可以参考jsets-shiro-demo示例源码来了解如何在具体项目中应用Shiro进行。另外,可以查看使用说明来了解jsets-shiro-spring-boot-starter的具体使用方法。引用中提到了一种防止重放攻击的方法,通过将token的ID放入缓存(如redis、memcached)进行阅后即焚或销毁缓存来确保token只能使用一次。这是一种常见的防止重放攻击的解决方案。通过这些参考资料,你可以更好地理解和实践Shiro分布式的方案。123 #### 引用[.reference_title] - *1* *2* [shiro jwt 构建无状态分布式体系](https://blog.csdn.net/wj596/article/details/84914572)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *3* [分布式JWT整合shiro](https://blog.csdn.net/weixin_43769053/article/details/107643606)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值