栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Shiro整合JWT

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Shiro整合JWT

1、概述

Servlet的Session机制流程如下

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

引入Shiro后,新的流程如下

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

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

2、整合流程

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

@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;
}
public class NoSessionWebSubjectFactory extends DefaultWebSubjectFactory {
    @Override
    public Subject createSubject(SubjectContext context) {
        // 禁用session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

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

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相关的工具类

public class JwtUtil {
    
    public static final String ACCOUNT = "userName";
    
    public final static String CURRENT_TIME_MILLIS = "currentTimeMillis";
    
    public static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L;
    
    public static final String SECRET_KEY = "shiroKey";


    
    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);
    }

    
    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;
    }

    
    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验证

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {
    
    protected static final String AUTHORIZATION_HEADER = "Access-Token";

    
    @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;
    }

    
    @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中验证jwt,role,permission

@Component
public class CustomRealm extends AuthorizingRealm {
    @Resource
    UserServiceImpl userServiceImpl;

    
    @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中

@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //给filter设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    //添加自定义过滤器
    Map filterMap = new HashMap<>(1);
    filterMap.put("jwt", new JwtFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    //配置系统受限资源和公共资源
    Map 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;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/489346.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号