Shiro集成springboot的一些配置和问题

首先贴上官网地址https://shiro.apache.org/spring-framework.html

一、shiro配置类

要使用shiro必须配置一个shiroConfig配置类,中shiro配置的类中需要三个bean:

  • Realm,自定义的Realm
  • DeaultWebSecurityManager,需要传递Realm参数
  • ShiroFilterFactoryBean,需要传递DeaultWebSecurityManager参数
    • 在ShiroFilterFactoryBean里面我们可以选择添加对应的过滤器
      • anon : 无需认证,就可以访问
      • authc : 必须认证,才能访问
      • user : 必须拥有 “记住我”功能才能用
      • perms : 拥有对某个资源的权限才能访问
      • role : 拥有某个角色权限才能访问
@Configuration
public class ShiroConfig {

    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    @Bean
    public DefaultWebSecurityManager defaultSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultSecurityManager") DefaultWebSecurityManager SecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(SecurityManager);

        //添加 Shiro 的内置过滤器=======================
        /*
            anon : 无需认证,就可以访问
            authc : 必须认证,才能访问
            user : 必须拥有 “记住我”功能才能用
            perms : 拥有对某个资源的权限才能访问
            role : 拥有某个角色权限才能访问
         */
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/back/**","authc");
        map.put("/front/**","anon");
        // 设置 /user/addUser 这个请求,只有认证过才能访问
        //        map.put("/user/addUser","authc");
        //        map.put("/user/deleteUser","authc");
        // 设置 /user/ 下面的所有请求,只有认证过才能访问
        //        map.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(map);
        // 设置登录的请求
        bean.setLoginUrl("/login");
        //============================================
        bean.setUnauthorizedUrl("/noPermission");
        return bean;
    }

    //thymeleaf集成shiro
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }


    @Bean
    public ShiroExceptionResolver shiroExceptionResolver(){
        return new ShiroExceptionResolver();
    }

}

shiro内置的几种过滤器

anon : 无需认证,就可以访问
authc : 必须认证,才能访问
user : 必须拥有 “记住我”功能才能用
perms : 拥有对某个资源的权限才能访问
role : 拥有某个角色权限才能访问

二、shiro-realm认证和授权

自定义的Realm需要集成AuthorizingRealm,重写以下方法

  • doGetAuthorizationInfo(PrincipalCollection principalCollection)Authorization授权
    • 首先doGetAuthorizationInfo需要return一个AuthorizationInfo对象,我们先new一个他的实现类对象:SimpleAuthorizationInfo
      • Shiro集成springboot的一些配置和问题
    • 授权需要我们拿到登录对象—>登录用户:SecurityUtils.getSubject(); ,***(User)subject.getPrincipal();***
    • SimpleAuthorizationInfo里面有授权,授予角色的函数
      • addRole(String role)
      • setRoles(Set<String> set)
      • addStringPermissions(String permission)
      • addObjectPermissions(Set<Permission>)
    • 最后对用户授权完毕后返回info
  • doGetAuthenticationInfo(AuthenticationToken authenticationToken)Authentication认证
    • 首先doGetAuthenticationInfo需要return一个AuthenticationInfo对象,我们先new一个他的实现类对象:SimpleAuthenticationInfo
      • Shiro集成springboot的一些配置和问题
      • 接着看这个实现类的构造函数Shiro集成springboot的一些配置和问题
        • 常用的前两个,传递给该构造函数一个用户principal,credentials登录凭证,一个realName当前登录的角色名,这个在下一层会封装进SimplePrincipalCollection集合里面
    • 所以此时我们需要获取数据库的用户信息,将其交给SimpleAuthenticationInfo来进行验证
    • 登录验证时,我们在Controller里对接收的用户名密码封装为shiro里面的UsernamePasswordToken令牌(此时并未验证),之后Realm就能在后面doGetAuthenticationInfon拿到登录的令牌
    • doGetAuthenticationInfon拿到登录的令牌能获取用户名,我们就通过查询用户名获取密码进行验证
      • 用户名不存在抛出UnknownAccountException异常
      • 密码错误抛出IncorrectCredentialsException异常
    • 我们就可以捕获异常进行相应的处理

LoginController

@PostMapping("/LoginConfirm")
    public String login(String username, String password, Model model) {
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //没有认证过
        //封装用户的登录数据,获得令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //登录 及 异常处理
        try {
            //用户登录
            subject.login(token);
            Session subjectSession = subject.getSession();
            model.addAttribute("loginUser",subjectSession.getAttribute("loginUser"));
            return "redirect:index";
        } catch (UnknownAccountException uae) {
            log.warn("用户名不存在");
            //如果用户名不存在
            System.out.println("用户名不存在");
            model.addAttribute("exception", "用户名不存在");
            return "redirect:login";
        } catch (IncorrectCredentialsException ice) {
            log.warn("密码错误");
            //如果密码错误
            System.out.println("密码错误");
            model.addAttribute("exception", "密码错误");
            return "redirect:login";
        }
}

UserRealm

public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("================执行了授权====================");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        // 拿到User对象
        User currentUser = (User) subject.getPrincipal();
        // 设置当前用户的权限
        String permission = currentUser.getPermission();
        System.out.println(currentUser.getUsername() + "的权限为 " + permission);
        info.addStringPermission(permission);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("=====================执行了认证AuthenticationToken===================");

        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        //连接真实的数据库
        User user = userService.getUserByName(userToken.getUsername());
        if(user == null){
            //没有这个人
            return null; //抛出异常 UnknownAccountException
        }

        // 登录成功 将用户信息存入session
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser",user);

        // 密码认证,shiro做
        return new SimpleAuthenticationInfo(user,user.getPassword(),"");
    }
}

三、shiro-thymeleaf

配置:

要想在前端使用shiro的标签要进行如下配置

在刚才的shiro配置类里新增如下Bean

@Configuration
public class ShiroConfig {

    //thymeleaf集成shiro
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

}

在thymeleaf前端<html>标签里加入:

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

Shiro集成springboot的一些配置和问题

常用标签

<shiro:guest>
    游客访问 <a href = "login.jsp"></a>
</shiro:guest>
 
user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<shiro:user>
    欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>
 
authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<shiro:authenticted>
    用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticted>
 
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<shiro:notAuthenticated>
    未身份验证(包括"记住我")
</shiro:notAuthenticated>
 
 
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<shiro:principal property = "username"/>
 
hasRole标签:如果当前Subject有角色将显示body体内的内容
<shiro:hashRole name = "admin">
    用户[<shiro:principal/>]拥有角色admin
</shiro:hashRole>
 
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<shiro:hasAnyRoles name = "admin,user">
    用户[<shiro:pricipal/>]拥有角色admin 或者 user
</shiro:hasAnyRoles>
 
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<shiro:lacksRole name = "admin">
    用户[<shiro:pricipal/>]没有角色admin
</shiro:lacksRole>
 
hashPermission:如果当前Subject有权限将显示body体内容
<shiro:hashPermission name = "user:create">
    用户[<shiro:pricipal/>] 拥有权限user:create
</shiro:hashPermission>
 
lacksPermission:如果当前Subject没有权限将显示body体内容
<shiro:lacksPermission name = "org:create">
    用户[<shiro:pricipal/>] 没有权限org:create
</shiro:lacksPermission>	

四、开发过程中遇到的问题

1、ShiroFilterFactoryBean配置了setUnauthorizedUrl不起作用

启发来源:https://blog.csdn.net/bicheng4769/article/details/86680955

问题重现:

在ShiroFilterFactoryBean中配置setUnauthorizedUrl,顾名思义未授权则跳转至对应请求路径

Shiro集成springboot的一些配置和问题

但是却发现无论怎么样,无权限的只会报错而不会跳到配置的那个请求

原因追溯:

我们先点进setUnauthorizedUrl看源码,发现这个url在applyUnauthorizedUrlIfNecessary里面使用

Shiro集成springboot的一些配置和问题

我们发现AuthorizationFilter这个过滤器使用了该url,点进去看实现类

Shiro集成springboot的一些配置和问题

发现AuthorizationFilter下的实现类只有Port,Premission,Role,Ssl这些过滤器;

而我在前面配置的FilterChainDefinitionMap里面是authc和anon类型的,不在该过滤器管理范围内,那么就会一直抛出异常org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method

解决:

手动捕获异常进行页面跳转

ShiroExceptionResolver实现HandlerExceptionResolver,当遇到该异常时手动跳转至位授权的页面

public class ShiroExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("==============异常开始=============");
        //如果是shiro无权操作,因为shiro 在操作auno等一部分不进行转发至无权限url
        if(ex instanceof UnauthorizedException){
            ModelAndView mv = new ModelAndView("redirect:/noPermission");
            return mv;
        }
        ex.printStackTrace();
        System.out.println("==============异常结束=============");
        ModelAndView mv = new ModelAndView("redirect:/err");
        mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
        return mv;
    }
}

总结:

perms,roles,ssl,rest,port 属于AuthorizationFilter

anon,authcBasic,auchc,user 属于 AuthenticationFilter

上一篇:shiro mgt包下RealmSecurityManager类


下一篇:Shrio学习二