0
点赞
收藏
分享

微信扫一扫

你知道什么是shiro吗?手把手教你集成shiro

朱小落 2022-04-14 阅读 67

Shiro简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前,使用 Apache Shiro 来的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。shiro由三大组件构成:Subject、SecurityManager、Realm。

Subject:可以理解为主体或者用户,是一个抽象概念,这个用户不一定是一个具体的人,可以是与当前应用交互的任何东西。

SecurityManager:安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互;是 Shiro 的核心,它管理着所有 Subject,且负责进行认证和授权、会话、缓存的管理。

Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),也就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

总结一下,就是最简单的一个 Shiro 应用:

  1. 应用代码通过 Subject 来进行认证和授权,然后 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

接下来就让我们在代码层面继承一下shiro。首先我们要准备好环境,创建一个简单的springboot工程,配置及数据库操作这里不再赘述,有需要的可以后台私聊阿Q呦。

集成Shiro

一、引入shiro的依赖包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>

二、自定义Realm进行权限认证和身份认证

@Slf4j
public class CustomRealm extends AuthorizingRealm {
     @Autowired
    private SysUserInfoMapper userInfoMapper;

    /**
     *  权限认证/获取授权信息
     *  该方法只有在需要权限认证时才会进入,
     *  比如前面配置类中配置了
     *  filterChainDefinitionMap.put("/admin/**", "roles[admin]"); 的管理员角色,
     *  这时进入 /admin 时就会进入该方法来检查权限
     * @author cheetah
     * @date 2020/11/21
     * @param principals:
     * @return: org.apache.shiro.authz.AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("————权限认证————");
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得该用户角色
        String role = userInfoMapper.getRole(username);
        Set<String> set = new HashSet<>();
        //需要将 role 封装到 Set 作为 info.setRoles() 的参数
        set.add(role);
        //设置该用户拥有的角色
        info.setRoles(set);
        return info;
    }

    /**
     *  身份认证/获取身份验证信息
     *  Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
     *  该方法则是需要身份认证时(比如前面的 Subject.login() 方法)才会进入
     * @author cheetah
     * @date 2020/11/21
     * @param authenticationToken:  用户身份信息 token
     * @return: org.apache.shiro.authc.AuthenticationInfo
     * 返回封装了用户信息的 AuthenticationInfo 实例
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("————身份认证方法————");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 从数据库获取对应用户名密码的用户
        SysUserInfo userInfo = userInfoMapper.getUserByLogin(token.getUsername());
        if (null == userInfo) {
            throw new BusinessException(CommonResultStatus.USERNAME_ERROR);
        } else if (!userInfo.getPassword().equals(new String((char[]) token.getCredentials()))) {
            throw new BusinessException(CommonResultStatus.PASSWORD_ERROR);
        }
        return new SimpleAuthenticationInfo(token.getPrincipal(), userInfo.getPassword(), getName());
    }
}

三、创建ShiroConfig,将ShiroFilterFactoryBean交由spring管理;将自定义的身份认证交由SecurityManager管理

代码如下:

@Configuration
@Slf4j
public class ShiroConfig {

    /**
     *  创建ShiroFilterFactoryBean
     * @author cheetah
     * @date 2020/11/21
     * @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        //设置shiro内置过滤器
        Map<String, Filter> filters = new LinkedHashMap<>();
        //增加自定义过滤器,只对需要登陆的接口进行过滤
        filters.put("authc", new CustomRolesOrAuthorizationFilter());
        //filters.put("roles", new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filters);

        // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/adminLogin/login");
        // 设置无权限时跳转的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/notAuth");

        // 设置拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //游客,开发权限
        filterChainDefinitionMap.put("/guest/**", "anon");
        //用户,需要角色权限 “user”
        filterChainDefinitionMap.put("/user/**", "roles[user]");
        //管理员,需要角色权限 “admin”
        filterChainDefinitionMap.put("/admin/**", "roles[admin]");
        //商品,需要角色权限 “user”
        //filterChainDefinitionMap.put("/productInfo/**", "roles[user]");
        //开放登陆接口
        filterChainDefinitionMap.put("/adminLogin/login", "anon");
        //其余接口一律拦截
        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        log.info("-------Shiro拦截器工厂类注入成功-----------");
        return shiroFilterFactoryBean;
    }

    /**
     *  注入安全管理器
     * @author cheetah
     * @date 2020/11/21
     * @return: java.lang.SecurityManager
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     *  注入自定义身份认证
     * @author cheetah
     * @date 2020/11/21
     * @return: com.cheetah.shiroandjwt.config.CustomRealm
     */
    @Bean
    public CustomRealm customRealm(){
        return new CustomRealm();
    }

}

四、自定义的过滤器代码如下:

public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter{

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //验证用户是否登陆,若是未登陆直接返回异常信息
        Subject subject = getSubject(request, response);
        Object principal = subject.getPrincipal();
        if(principal==null){
            return false;
        }
        //获取当前访问路径所需要的角色集合
        String[] rolesArray = (String[]) mappedValue;
        //没有角色限制,可以直接访问
        if (rolesArray == null || rolesArray.length == 0) {
            //没有指定角色的话不需要进行验证,放行
            return true;
        }

        Set<String> roles = CollectionUtils.asSet(rolesArray);
        //当前subject是roles中的任意一个,则有权限访问
        for(String role : roles){
            if(subject.hasRole(role)){
                return true;
            }
        }
        return false;
    }

}

五、接下来根据业务来测试一下shiro的相关功能,附上业务测试代码:登陆代码+商品列表代码

@RestController
@RequestMapping("adminLogin")
public class LoginController {

    @Autowired
    private UserInfoService userInfoService;

    @PostMapping("login")
    public AjaxResult login(@RequestBody SysUserInfo userInfo){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getName(),userInfo.getPassword());
        subject.login(token);
        return AjaxResult.success("登陆成功");
    }
}
@RestController
@RequestMapping("/productInfo")
public class ProductInfoController {

    @Autowired
    private ProductInfoService productInfoService;

    @RequestMapping("/getProductList")
    public AjaxResult getProductList(){
        List<ProductInfo> list = productInfoService.getProductInfoList();
        return AjaxResult.success(list);
    }

}

(1)首先验证登录:当调用subject.login(token);方法时,会走CustomRealm中的doGetAuthenticationInfo()

你是否也有这样的疑惑呢?成功登录之后,再次请求时,服务器是如何知道已经登录,是哪个用户,是使用HttpSession还是shiro的Session的呢?

在处理请求时,ShiroFilterFactoryBean实现了FactoryBean接口,在加载ShiroFilterFactoryBean时实际会加载SpringShiroFilter并添加到应用的过滤器链中。当有请求进来的时候,都会被SpringShiroFilter拦截到。Subject值就是在SpringShiroFilter拦截的过程中设置到线程变量中的。SpringShiroFilter的拦截方法中最关键的两步是createSubject和bind到ThreadContext里。到这里,可以理出大致的流程,用户发起请求->调用SpringShiroFilter的doFilter->创建Subject->设置到线程变量中,方便在后面取出使用。

创建subject可以分为以下三步:

(2)shiro实际上是使用session来传递登陆信息的,接下来我们来看一下

当我们在header中传入jsessionid参数时才能获取到访问的数据。

(3)重点:ShiroConfig中ShiroFilterFactoryBean的讲解:

当代码中有
filterChainDefinitionMap.put("/productInfo/**", “roles[user]”);时,代表商品列表需要权限验证,此时不会去走自定义的过滤器;

而当将代码中的 filters.put(“authc”, new 
CustomRolesOrAuthorizationFilter());

改为filters.put(“roles”, new 
CustomRolesOrAuthorizationFilter()); 时,代码会先去走该过滤器进行权限验证,isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)中的mappedValue便是roles[user]中括号里的集合。

如果感觉小编写得不错,请素质三连:点赞+转发+关注。我会努力写出更好的作品分享给大家。更多JAVA进阶学习资料小编已打包好,可以关注私信找我领取哦!

举报

相关推荐

0 条评论