ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Shiro整合JWT

2021-11-13 17:30:14  阅读:119  来源: 互联网

标签:userName return String JWT token 整合 new public Shiro


1、概述

ServletSession机制流程如下

  • 用户首次发请求
  • 服务器接收到请求之后,无论你有没有权限访问到资源,在返回响应的时候,服务器都会生成一个Session用来储存该用户的信息,然后生成SessionId作为对应的Key
  • 服务器会在响应中,用SessionId这个名字,把这个SessionIdCookie的方式发给客户(就是Set-Cookie响应头)
  • 由于已经设置了Cookie,下次访问的时候,服务器会自动识别到这个SessionId然后找到你上次对应的Session

引入Shiro后,新的流程如下

  • 用户首次发请求。
  • 服务器接收到请求之后,无论你有没有权限访问到资源,在返回响应的时候,服务器都会生成一个Session用来储存该用户的信息,然后生成SessionId作为对应的Key,还会创建一个Subject对象(就是Shiro中用来代表当前用户的类),也用这个SessionId作为Key绑定。
  • 服务器会在响应中,用SessionId这个名字,把这个SessionIdCookie的方式发给客户(就是Set-Cookie响应头)。
  • 第二次接受到请求的时候,Shiro会从请求头中找到SessionId,然后去寻找对应的Subject然后绑定到当前上下文,这时候Shiro就能知道来访的是谁了。

对于以上流程,都和浏览器中的Cookie密切相关的,对于一个前后端分离的系统而言,一般是需要支持多端的,一个api要支持H5, PCAPP三个前端,如果使用session的话对app不是很友好,而且session有跨域攻击的问题

2、整合流程

Shiro集成JWT需要禁用session,禁用后服务器将不会再维护用户的状态,达到无状态调用的目的

/**
 * @param realm
 * @return DefaultWebSecurityManager
 * @description 注入安全管理器
 * @author PengHuAnZhi
 * @date 2021/11/4 20:50
 */
@Bean("defaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomRealm realm) {
    DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
    //开启全局缓存
    realm.setCachingEnabled(true);
    //开启认证缓存
    realm.setAuthenticationCachingEnabled(true);
    realm.setAuthenticationCacheName("authenticationCache");
    //开启授权缓存
    realm.setAuthorizationCachingEnabled(true);
    realm.setAuthorizationCacheName("authorizationCache");
    realm.setCacheManager(new EhCacheManager());
    defaultSecurityManager.setRealm(realm);
    //关闭shiro自带的session
    DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
    defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
    subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
    defaultSecurityManager.setSubjectDAO(subjectDAO);
    return defaultSecurityManager;
}
/**
 * @author PengHuAnZhi
 * @ProjectName ShiroSpringBootDemo
 * @Description TODO
 * @time 2021/11/13 16:25
 */
public class NoSessionWebSubjectFactory extends DefaultWebSubjectFactory {
    @Override
    public Subject createSubject(SubjectContext context) {
        // 禁用session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

然后定义一个JwtToken,用于封装UserNameToken,并且要使其充当Shiro中的令牌,所以需要实现AuthenticationToken接口,重写里面的获取用户信息getPrincipal和获取凭证信息的getCredentials两个方法

/**
 * @author PengHuAnZhi
 * @ProjectName ShiroSpringBootDemo
 * @Description TODO
 * @time 2021/11/13 15:36
 */
public class JwtToken implements AuthenticationToken {
    private final String token;

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

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

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

定义Jwt相关的工具类

/**
 * @author PengHuAnZhi
 * @ProjectName ShiroSpringBootDemo
 * @Description TODO
 * @time 2021/11/13 15:49
 */
public class JwtUtil {
    /**
     * JWT-account
     */
    public static final String ACCOUNT = "userName";
    /**
     * JWT-currentTimeMillis
     */
    public final static String CURRENT_TIME_MILLIS = "currentTimeMillis";
    /**
     * 有效期时间2小时
     */
    public static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L;
    /**
     * 秘钥
     */
    public static final String SECRET_KEY = "shiroKey";


    /**
     * 生成签名返回token
     *
     * @param userName          用户名
     * @param currentTimeMillis 当前时间戳
     * @return 返回一个token
     */
    public static String sign(String userName, String currentTimeMillis) {
        // 帐号加JWT私钥加密
        String secret = userName + SECRET_KEY;
        // 此处过期时间,单位:毫秒,在当前时间到后边的20分钟内都是有效的
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        //采用HMAC256加密
        Algorithm algorithm = Algorithm.HMAC256(secret);
        return JWT.create()
                .withClaim(ACCOUNT, userName)
                .withClaim(CURRENT_TIME_MILLIS, currentTimeMillis)
                .withExpiresAt(date)
                //创建一个新的JWT,并使用给定的算法进行标记
                .sign(algorithm);
    }

    /**
     * 校验token是否正确
     *
     * @param token token
     * @return 是否登录成功
     */
    public static boolean verify(String token) {
        String secret = getClaim(token, ACCOUNT) + SECRET_KEY;
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .build();
        verifier.verify(token);
        return true;
    }

    /**
     * 获得Token中的信息无需secret解密也能获得
     *
     * @param token token
     * @param claim token中的信息
     * @return 返回解密后的信息
     */
    public static String getClaim(String token, String claim) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(claim).asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }
}

然后再定义一个JwtFilter用于过滤所有请求,继承BasicHttpAuthenticationFilter,将其交给Shiro验证

/**
 * @author PengHuAnZhi
 * @ProjectName ShiroSpringBootDemo
 * @Description TODO
 * @time 2021/11/13 15:39
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {
    /**
     * 设置请求头中需要传递的字段名
     */
    protected static final String AUTHORIZATION_HEADER = "Access-Token";

    /**
     * @param request     请求
     * @param response    响应
     * @param mappedValue 就是[urls]配置中拦截器参数部分
     * @return boolean 表示是否允许访问,如果允许访问返回true,否则false
     * @date 2020/11/24
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        JwtToken token = new JwtToken(((HttpServletRequest) request).getHeader(AUTHORIZATION_HEADER));
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
     * @param request  请求
     * @param response 响应
     * @return boolean 表示当访问拒绝时是否已经处理了,如果返回true表示需要继续处理,如果返回false表示该拦截器实例已经处理了,将直接返回即可
     * @date 2020/11/24
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        String jsonWebToken = ((HttpServletRequest) request).getHeader(AUTHORIZATION_HEADER);
        String username = "";
        if (StringUtils.isBlank(jsonWebToken)) {
            jsonWebToken = "";
        } else {
            // 解码 jwt
            DecodedJWT decodeJwt = JWT.decode(jsonWebToken);
            username = decodeJwt.getClaim("userName").asString();
            System.out.println(username + "登录");
        }
        JwtToken token = new JwtToken(jsonWebToken);
        try {
            // 交给自定义realm进行jwt验证和对应角色,权限的查询
            getSubject(request, response).login(token);
        } catch (AuthenticationException e) {
            request.setAttribute("msg", "认证失败");
            // 转发给指定的 controller, 进行统一异常处理
            request.getRequestDispatcher("/exception").forward(request, response);
            return false;
        }
        return true;
    }
}

在自定义Realm中验证jwtrolepermission

/**
 * @author PengHuAnZhi
 * @ProjectName ShiroSpringBootDemo
 * @Description TODO
 * @time 2021/11/4 20:44
 */
@Component
public class CustomRealm extends AuthorizingRealm {
    @Resource
    UserServiceImpl userServiceImpl;

    /**
     * @param token jwt
     * @return boolean
     * @description 验证是不是自己的token类型
     * @author PengHuAnZhi
     * @date 2021/11/13 16:06
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取用户名, 用户唯一标识
        String username = JwtUtil.getClaim(principals.toString(), "userName");
        User user = userServiceImpl.findByUserName(username);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        if (user == null) {
            return null;
        }
        authorizationInfo.addRole("admin");
        authorizationInfo.addStringPermission("user:update:*");
        authorizationInfo.addStringPermission("product:*:*");
        return authorizationInfo;
    }

    @SneakyThrows
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String credentials = (String) token.getCredentials();
        String userName;
        try {
            //jwt验证token
            boolean verify = JwtUtil.verify(credentials);
            if (!verify) {
                throw new AuthenticationException("Token校验不正确");
            }
            userName = JwtUtil.getClaim(credentials, JwtUtil.ACCOUNT);
        } catch (Exception e) {
            throw new Exception("用户身份校验失败");
        }

        //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,不设置则使用默认的SimpleCredentialsMatcher
        return new SimpleAuthenticationInfo(
                //用户名
                userName,
                //凭证
                credentials,
                //realm name
                this.getName());
    }
}

定义一个登录接口

@RequestMapping("/login")
public String login(String userName, String password, HttpServletResponse response) {
    try {
        if (!"phz".equals(userName) || !"123".equals(password)) {
            System.out.println("用户名错误");
            return "redirect:/login.jsp";
        }
        //生成token
        String token = JwtUtil.sign(userName, System.currentTimeMillis());
        //写入header
        response.setHeader("Access-Token", token);
        response.setHeader("Access-Control-Expose-Headers", "Access-Token");
    } catch (IncorrectCredentialsException e) {
        e.printStackTrace();
        System.out.println("密码错误");
        return "redirect:/login.jsp";
    } catch (UnknownAccountException e) {
        e.printStackTrace();
        System.out.println("用户名错误");
        return "redirect:/login.jsp";
    }
    return "redirect:/index.jsp";
}

最后将自己定义的Filter添加到Shiro

/**
 * @return ShiroFilterFactoryBean
 * @description 1、创建ShiroFilter,拦截所有请求
 * @author PengHuAnZhi
 * @date 2021/11/4 20:43
 */
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //给filter设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    //添加自定义过滤器
    Map<String, Filter> filterMap = new HashMap<>(1);
    filterMap.put("jwt", new JwtFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    //配置系统受限资源和公共资源
    Map<String, String> map = new HashMap<>();
    map.put("/register.jsp", ANON);
    map.put("/login.jsp", ANON);
    map.put("/user/login", ANON);
    map.put("/user/register", ANON);
    //使用自己的filter对所有请求拦截
    map.put("/**", "jwt");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    //不设置默认也是login.jsp
    shiroFilterFactoryBean.setLoginUrl("/login.jsp");
    return shiroFilterFactoryBean;
}

标签:userName,return,String,JWT,token,整合,new,public,Shiro
来源: https://blog.csdn.net/qq_43509535/article/details/121307403

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有