分布式鉴权之JWT整合shiro

分布式鉴权之JWT整合shiro

  • 关于Jwt
    • Jwt是什么
    • Jwt结构
    • Jwt应用
  • Jwt整合Shiro
    • 自定义Token-JwtToken
    • ShiroConfig配置类
    • JwtFilter
    • JwtRealm
  • 问题解决
    • 问题验证
    • 解决方案
  • 另附

关于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 filters=new TreeMap<>();
        filters.put("jwt",new JwtFilter());
        filters.put("roles",new RolesFilter());
        bean.setFilters(filters);
        // 路径拦截顺序,一定要使用LinkedHashMap,否则时而拦截,时而不拦截
        Map 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 strRoleList=new ArrayList<>();
        // 权限集合
        List strPerList=new ArrayList<>();
        // 获取用户的角色集合
        List 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路径进行拦截
如下图:
分布式鉴权之JWT整合shiro_第1张图片
已经拦截成功说明拦截器生效。
因为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,如下图:
分布式鉴权之JWT整合shiro_第2张图片
接着再次访问需要roles[root]角色的接口,如下图:
分布式鉴权之JWT整合shiro_第3张图片
从图中可以看出RolesFilter生效

解决方案

在ShiroConfig中手动加入

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

并进行如下更改
分布式鉴权之JWT整合shiro_第4张图片
问题解决,,, 哈哈哈,超级开心

另附

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

你可能感兴趣的:(jwt,shiro,分布式)