Shiro框架理解:身份验证(Authentication)及授权(Authorization)

目录

 

一、介绍:

执行流程:

1. Shiro的核心组件

2. Shiro 的核心安全管理器 SecurityManager

二、验证(Authentication):

OAuth2Filter extends AuthenticatingFilter:

1. 拦截请求

  2. 验证token是否有效

 executeLogin 方法:

 3. 调用 Realm 进行认证(OAuth2Realm)

三、授权(Authorization)与鉴权:

        1. 在Shiro配置类当中配置权限注解支持:

四、配置过滤器链 


一、介绍:

        shiro框架有身份验证授权会话管理以及加密等功能。

        下面是我对身份验证(Authentication) 和授权(Authorization)功能的解析。如有错误还大家评论区斧正。

执行流程:

 

1. Shiro的核心组件

  1. Subject:代表当前用户,可以是用户、程序或第三方服务。
  2. SecurityManager:整个Shiro框架的核心,用于管理所有安全操作。
  3. Realm:数据源,负责连接实际的数据存储(如数据库、LDAP)以验证和获取用户的权限数据。

2. Shiro 的核心安全管理器 SecurityManager

@Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
        // 使用默认的 Web 安全管理器
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义的 Realm,用于身份验证和授权
        securityManager.setRealm(oAuth2Realm);
        // 禁用 RememberMe 功能
        securityManager.setRememberMeManager(null);
        return securityManager; // 返回安全管理器
    }

二、验证(Authentication):

        Shiro 框架实现了基于 OAuth2 和 JWT 的身份验证流程,主要包括OAuth2TokenOAuth2RealmOAuth2FilterShiroConfigThreadLocalToken等组件。

OAuth2Filter extends AuthenticatingFilter:

1. 拦截请求

        获取请求中的token,如果token为空则返回null,否则返回一个 OAuth2Token对象。 

        createToken():

@Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String requestToken = getRequestToken(req);
        if (StringUtils.isBlank(requestToken)){
            return null;
        }
        return new OAuth2Token(requestToken);
    }

         OAuth2Token:

public class OAuth2Token implements AuthenticationToken {

    private String token;

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

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

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

  2. 验证token是否有效

         如果token不存在,直接返回401。如果令牌过期,则到Redis当中查看是否存在令牌(这里包含令牌刷新功能,在创建token时,同时将token保存到JWT和Redis当中,并设置Redis中的token过期时间为JWT中的两倍,如果用户在JTW中token未过期时间里登录则会校验JWT中的token,如果超过这个时间就会去Redis当中查找token,如果token存在则刷新令牌,否则需要用户重新进行登录)。

        如果 Token 合法,调用 executeLogin() 方法,将 Token 提交到 Shiro 的安全管理器

        onAccessDenied :

@Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        resp.setHeader("Content-type","text/html;charset=UTF-8");
        //允许跨域请求
        resp.setHeader("Access-Control-Allow-Origin", resp.getHeader("Origin"));
        resp.setHeader("Access-Control-Allow-Credentials", "true");

        //获取请求token,如果请求里没有token,直接返回401
        String token = getRequestToken(req);
        try {
            jwtUtil.verifyToken(token); //检查令牌是否过期
        }catch (TokenExpiredException e){ //如果令牌过期,检查Redis中是否有令牌,如果存在,就重新生产一个令牌给客户端
            if (redisTemplate.hasKey("token")){ //Redis中有令牌,更新令牌
                redisTemplate.delete(token);//删除Redis令牌
                int userId = jwtUtil.getUserId(token);
                String newToken = jwtUtil.createToken(userId);//生成新的Redis令牌
                redisTemplate.opsForValue().set("token", newToken);//将新令牌保存到Redis中
                threadLocalToken.setToken(newToken);//将新令牌保存到Threadlocal中
            }else {//Redis中没有令牌
                resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                resp.getWriter().print("令牌已过期");
                return false;
            }
        }catch (Exception e){//如果Redis不存在令牌,让用户重新登录
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            resp.getWriter().print("无效令牌");
            return false;
        }
        boolean bool = executeLogin(request, response);
        return bool;
    }
 executeLogin 方法:
  •         executeLogin 会尝试创建认证令牌(通过 createToken 方法)并交给 Shiro 的 SecurityManager 进行认证。
  •         如果认证成功,doGetAuthenticationInfo 方法会被调用以完成认证。
  •         如果认证失败,executeLogin 会捕获认证异常,然后触发 onLoginFailure 方法。

         onLoginFailure:

@Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        resp.setHeader("Content-type","text/html;charset=UTF-8");
        //允许跨域请求
        resp.setHeader("Access-Control-Allow-Origin", resp.getHeader("Origin"));
        resp.setHeader("Access-Control-Allow-Credentials", "true");
        try {
            resp.getWriter().print(e.getMessage());
        } catch (IOException ex) {

        }
        return false;
    }

 3. 调用 Realm 进行认证(OAuth2Realm)

        如果用户信息有效,则返回SimpleAuthenticationInfo对象,包含用户信息和token。返回的认证信息有效,Shiro 将认证视为成功。

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {
        //从令牌中获取用户userId,检测用户是否被冻结
        String accessToken = (String) Token.getPrincipal();
        int userId = jwtUtil.getUserId(accessToken);
        TbUser tbUser = userService.selectById(userId);
        if (tbUser == null) {
            throw new LockedAccountException("账号已被锁定,请联系管理员");
        }
        // 将token信息,用户信息加入到info当中
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(tbUser, accessToken, this.getName());
        return info;
    }

         这里有一个需要注意的点,校验完token后,通过executeLogin()将OAth2Token交给SecurityManager管理,这是SecurityManager会自动调用认证模块(触发绑定到SecurityManager的Realm,也就是OAuth2Realm,并调用其 doGetAuthenticationInfo() 方法)完成具体的认证逻辑。

三、授权(Authorization)与鉴权:

        1. 在Shiro配置类当中配置权限注解支持:

@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        // 设置安全管理器,用于注解支持
        advisor.setSecurityManager(securityManager);
        return advisor; // 返回配置好的 Advisor
    }

        配置好以后,当存在 @RequiresRoles、@RequiresPermissions时,就会对用户的权限进行校验,查看是否有权限使用该方法:

        例:

@RequiresPermissions("user:add")
public void addUser() {
    // ......
}

         SecurityManager交给Realm处理,会自动调用OAuth2Realm中的doGetAuthorizationInfo()方法,获取用户的权限并返回,最终与请求的的权限进行比对。

@Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection Collection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询用户权限列表
        TbUser user = (TbUser) Collection.getPrimaryPrincipal();
        Set<String> permissions = userService.searchUserPermissions(user.getId());
        //把用户权限加入到info当中
        info.setStringPermissions(permissions);
        return info;
    }

         上面写的是关于后端的鉴权,前端的鉴权可以参考我的另一篇博客 点击前往

四、配置过滤器链 

配置 Shiro 的核心过滤器工厂,用于定义过滤规则
@Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, OAuth2Filter oAuth2Filter) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilter.setSecurityManager(securityManager);

        // 配置自定义的 OAuth2 过滤器
        Map<String, Filter> filters = new HashMap<>();
        filters.put("oauth2", oAuth2Filter); // 定义 oauth2 过滤器
        shiroFilter.setFilters(filters);

        // 配置过滤链,定义访问权限
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/app/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/captcha.jpg", "anon");
        filterMap.put("/user/register", "anon");
        filterMap.put("/user/login", "anon");
        //filterMap.put("/test/**", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter; // 返回配置好的过滤器工厂
    }

         其中配置为“anon”的代表可以匿名访问,“oauth2”的必须经过 oAuth2Filter 处理才能访问。

        具体代码实现可参考点击前往

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值