#|Shiro学习与笔记


Shiro

  • Shiro
    • Shiro理论
      • 什么是shiro
      • 功能
      • 组件
      • shiro注解
      • shiro的优点
      • 运行流程
      • 前端标签
        • jsp页面
        • Thymeleaf
    • 在spring框架中集成shiro
      • Pom文件进行依赖配置
      • 集成Shiro
        • web.xml中的设置
        • shiro的bean配置文件,
    • Spring Boot Shiro
      • POM文件
      • 在application.properties设置shiro配置
      • MyRealm.java
      • ShiroConfig.java
      • 身份验证
        • Role 配合页面标签,控制不同用户访问不同页面
        • Resource 配合控制器注解,控制某个特殊的授权权限

Shiro Shiro理论 什么是shiro
Shiro是一个强大易用的java安全,提供了认证、授权、加密、会话管理、与web集成、缓存等功能,对于任何一个应用程序,都可以提供全面的安全服务,相比其他安全框架,shiro要简单的多。
功能
  • 身份验证(核心功能);
  • 资源授权(核心功能);
  • 密码加密(非核心功能);
  • 会话管理(非核心功能);
  • Remember Me(非核心功能);
组件
  • Subject:应用层和shiro框架交互的对象
  • Realm:实现身份验证和资源授权的核心组件:域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
  • RememberMeManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于spring mvc中的dispatcherServlet前端控制器。
    • SimpleCookie:记住我——Cookie;
    • CookieRememberMeManager:记住我—— Cookie 管理器;
  • SessionManager
    • SimpleCookie:会话管理—— Cookie;
    • DefaultWebSessionManager:会话配置;
  • SecurityManager:安全管理器,它负责与 Shiro 的其他组件进行交互,需要注入 Realm、RememberMeManager、SessionManager 等组件,且管理着所有的 Subject;
  • ShiroFilterFactoryBean:Shiro 过滤器工厂,注入 SecurityManager、定义过滤器、指定路径拦截规则等;
  • ShiroDialect:Shiro 方言,支持 Thymeleaf 页面 Shiro 标签;
  • DefaultAdvisorAutoProxyCreator:Advisor 代理类生成器;
  • AuthorizationAttributeSourceAdvisor:创建 Advisor 代理类,扫描 Shiro 注解;
  • 应用代码通过 Subject 来进行认证和授权,Subject 委托给 SecurityManager,我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能对用户及其权限进行判断;
#|Shiro学习与笔记
文章图片


shiro注解
  • @RequiresAuthentication : 表示当前 Subject 需要登录;
  • @RequiresUser : 表示当前 Subject 需要登录或记住我;
  • @RequiresGuest : 表示当前 Subject 是游客身份;
  • @RequiresRoles(value=https://www.it610.com/article/{“admin”, “user”}, logical=Logical.AND):当前 Subject 需要的角色;
  • @RequiresPermissions (value=https://www.it610.com/article/{““,””}, logical= Logical.OR) :当前 Subject 需要拥有的资源;
既可以用在controller中,也可以用在service中建议将shiro注解放入controller,因为如果service层使用了spring的事物注解,那么shiro注解将无效
shiro的优点
1、 简单的身份验证,支持多种数据源 2、对角色的简单授权,支持细粒度的授权(方法) 3、支持一级缓存,以提升应用程序的性能 4、内置基于POJO的企业会话管理,适用于web及非web环境 5、非常简单的API加密 6、不跟任何框架绑定,可以独立运行

运行流程
  1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
  2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
  3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
  5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 则抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
前端标签
jsp页面
首先要导入标签库; :导入标签库;:游客访问;:用户需要登录或记住我;:用户需要登录;:用户需要拥有某种角色访问;:用户需要拥有某些角色;:用户需要拥有某资源;进行显示

Thymeleaf
导入shiro:guest:游客访问; shiro:user:用户需要登录或记住我; shiro:authenticated:用户需要登录; shiro:hasRole:用户需要某角色; shiro:hasAnyRoles:用户需要某些角色; shiro:hasPermission:用户需要某权限; -----------------------------------------------------------
  • :用户需要 admin 或 manager 角色可访问;
  • shiro:principal/>
  • :身份验证器包装的用户名时,页面获取用户名;shiro:principal property="loginName"/>:身份验证器中包装 user 对象时,页面获取用户名;:页面获取 session 中 user 对象;

    在spring框架中集成shiro Pom文件进行依赖配置
    可以在maven仓库包https://mvnrepository.com/,根据三要素进行查找
    >1.7.1 org.apache.shiro shiro-core ${shiro.version} org.apache.shiro shiro-spring ${shiro.version} org.apache.shiro shiro-web ${shiro.version} org.apache.shiro shiro-aspectj ${shiro.version} org.apache.shiro shiro-ehcache ${shiro.version}

    项目跑起来了,只是控制台输出了
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

    需要配置日志包
    shiro使用slf4j作为日志框架,所以必需配置slf4j。同时,使用log4j作为底层的日志实现框架。
    org.slf4j slf4j-api 1.7.25 org.slf4j slf4j-log4j12 1.7.25 log4j log4j 1.2.17

    集成Shiro
    在Spring框架中集成Shiro,本质上是与Spring IoC容器和Spring MVC框架集成.
    web.xml中的设置
    shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /*

    shiro的bean配置文件, 其中包含过滤设置。此文件名为spring-shiro.xml,里面定义了需要的Bean,完成诸多功能。
    /login/view = anon /back/student/zzz =authc,roles[admin] /back/** = authc

    或者:springShiro.xml
    /static/**=anon/register=anon/login=anon/forgot=anon/logout=logout/api/**=anon/**=authc

    必须是在MVC配置文件中用

    将spring-shiro.xml载入。
    【#|Shiro学习与笔记】#|Shiro学习与笔记
    文章图片

    例子:
    • 身份验证
      UserRealm
      package com.qq.realm; import com.qq.model.User; import com.qq.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; /** * @description:com.qq.realm_studyssm * @author: 霏宇 * @time: 2022/8/5,9:55 */ public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权: @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String name =(String)principalCollection.getPrimaryPrincipal(); if (name.equals("admin")){ info.addStringPermission("admin"); }else if (name.equals("lisi")){ info.addStringPermission("worker"); info.addRole("admin"); } return info; } /** * 认证 * @param token * @return * @throws AuthenticationException */@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //一旦此用户不存在就主动抛出异常 UsernamePasswordToken upToken = (UsernamePasswordToken)token; String name =upToken.getUsername(); String password=new String(upToken.getPassword()); User user =userService.selectByName(name); if(null==user){ throw new UnknownAccountException("你输入的账户不存在!"); }else { User user2 =userService.selectByNamePassword(name,password); if(null==user2){ throw new IncorrectCredentialsException("密码错误"); }else { returnnew SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName()); } }//if(!name.equals("list")&&!name.equals("admin")){ //throw new UnknownAccountException("你输入的账户不存在!"); //}else { ////这里假设系统的用户只有两个 (zhangsan,123456)、(lisi,888999) //if(name.equals("list")&&password.equals("789") ||name.equals("admin")&&password.equals("123456")){ // //returnnew SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName()); // //}else { //throw new IncorrectCredentialsException("用户不存在"); //} // //}} }

      或者:MyRealm.java
      public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService; @Autowiredprivate RoleService roleService; @Autowiredprivate ResourceService resourceService; /*** -资源授权器*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 授权类SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principals.getPrimaryPrincipal(); if (user == null) {return null; }List roles = Optional.ofNullable(roleService.getRolesByUserId(user.getId())).orElse(Collections.emptyList()); roles.stream().forEach(role -> {authorizationInfo.addRole(role.getRoleName()); List resources = Optional.ofNullable(resourceService.getResourcesByRoleId(role.getId())).orElse(Collections.emptyList()); resources.stream().forEach(resource -> {authorizationInfo.addStringPermission(resource.getPermission()); }); }); return authorizationInfo; }/*** -身份验证器*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String userName = (String) token.getPrincipal(); User user = userService.getUserByUserName(userName); if (user == null) {throw new UnknownAccountException("The account do not exist."); }// realmName: 当前 realm 对象的唯一名字. 调用父类的 getName() 方法return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); }}

      LoginController
      package com.qq.controller; import com.qq.model.User; import com.qq.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession; /** * @description:com.qq.controller_studyssm * @author: 霏宇 * @time: 2022/8/4,14:02 */ @Controller @RequestMapping("/login") public class LoginController { @Autowired UserService userService; @RequestMapping("/view") String index(){ return "login/view"; }@RequestMapping("/enter") String enter(String name, String password, HttpSession session, Model model){Subject subject = SecurityUtils.getSubject(); // UsernamePasswordToken token = new UsernamePasswordToken(name, password); //对(用户名、密码)登录方式,用UsernamePasswordToken封装即可 try { subject.login(token); //调用login()方法。 有可能遭遇两种异常,代表登陆失败。 User user =userService.selectByNamePassword(name,password); session.setAttribute("USER",user); } catch (UnknownAccountException e) { e.printStackTrace(); model.addAttribute("message", "用户名错误!"); return "redirect:view"; } catch (IncorrectCredentialsException e) { e.printStackTrace(); model.addAttribute("message", "密码错误"); return "redirect:view"; }System.out.println(subject.isAuthenticated()); //打印下看看是否已认证成功return "redirect:/back/hui/index"; //User user = userService.selectByNamePassword(name, password); //if(user==null){ // //return "redirect:index"; //}else { //session.setAttribute("USER", user); //return "redirect:/back/book/index"; //}} @RequestMapping("/logout") String logout(HttpSession session){ session.removeAttribute("USER"); Subject subject =SecurityUtils.getSubject(); subject.logout(); // session.invalidate(); return "redirect:view"; }}

      或者在
      UserService
      public Result login(User user);

    ? UserServiceImpl
    ```java @Service public class UserServiceImpl implements UserService{@Overridepublic ResultEntity login(User user) {Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), MD5Util.getMD5(user.getPassword())); token.setRememberMe(user.getRememberMe()); return new Result(Result.ResultStatus.SUCCESS.code, "Success", user); try {subject.login(token); subject.checkRoles(); } catch (Exception e) {e.printStackTrace(); return new Result(Result.ResultStatus.FAILD.code, e.getMessage()); }Session session = subject.getSession(); session.setAttribute("user", subject.getPrincipal()); }@GetMapping("/logout")public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login"; }} ```

    Spring Boot Shiro POM文件
    org.apache.shiro shiro-core 1.4.0 org.apache.shiro shiro-spring 1.4.0 org.springframework.boot spring-boot-starter-thymeleaf com.github.theborakompanioni thymeleaf-extras-shiro 2.0.0

    在application.properties设置shiro配置
    #for shiro #开启Shiro配置,默认为true shiro.web.enabled=true#server.servlet.context-path=/shiro #spring.application.name=shiro

    MyRealm.java
    package com.feiyu.sprtingboot.config.shiro; import com.feiyu.sprtingboot.modules.account.entity.Resource; import com.feiyu.sprtingboot.modules.account.entity.Role; import com.feiyu.sprtingboot.modules.account.entity.User; import com.feiyu.sprtingboot.modules.account.service.ResourceService; import com.feiyu.sprtingboot.modules.account.service.RoleService; import com.feiyu.sprtingboot.modules.account.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @description:com.feiyu.sprtingboot.config.shiro_sprtingboot * @author: 霏宇 * @time: 2022/8/31,9:41 */ @Component public class MyRealmextends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private ResourceService resourceService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorization = new SimpleAuthorizationInfo(); // 从认证中获取当前的信息 User user = (User) principals.getPrimaryPrincipal(); // 从数据库查询当前用户的角色列表,并装载到资源授权器里 List roles = roleService.getRolesByUserId(user.getId()); roles.stream().forEach(item -> { authorization.addRole(item.getRoleName()); //authorization.addStringPermission(item.getRoleName()); // 再去查询每个角色拥有的资源列表,并装载到资源授权器里 List resources = resourceService.getResourcesByRoleId(item.getId()); resources.stream().forEach(it -> { authorization.addStringPermission(it.getPermission()); System.out.println(it.getPermission()); }); }); return authorization; }@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取用户名 String userName = (String) token.getPrincipal(); // 通过用户名查找数据库的 user 信息 User user = userService.getUserByUserName(userName); if (user == null) { throw new UnknownAccountException("User name is not exit."); }// 封装身份验证器 return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); } }

    ShiroConfig.java
    package com.feiyu.sprtingboot.config.shiro; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * @description:com.feiyu.sprtingboot.config.shiro_sprtingboot * @author: 霏宇 * @time: 2022/8/31,10:21 */ @Configuration public class ShiroConfig { @Autowired private MyRealm myRealm; @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); return securityManager; }/** * anon:匿名访问,无需登录 ---- AnonymousFilter * authc:登录后才能访问 ---- FormAuthenticationFilter * user:登录过能访问 ---- UserFilter * logout:登出 ---- LogoutFilter */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean(); // 注入安全管理器 filterFactory.setSecurityManager(securityManager()); // 设置登录页面、登录成功页面 filterFactory.setLoginUrl("/login"); filterFactory.setSuccessUrl("/test/thymeleafTest"); // 设置其余地址的访问策略 Map filterMap = new LinkedHashMap<>(); // 匿名策略 // 登录注册 filterMap.put("/login", "anon"); filterMap.put("/register", "anon"); // 静态资源 filterMap.put("/favicon.ico", "anon"); filterMap.put("/css/**", "anon"); filterMap.put("/images/**", "anon"); filterMap.put("/js/**", "anon"); filterMap.put("/vendors/**", "anon"); filterMap.put("/static/**", "anon"); // 测试模块 filterMap.put("/test/**", "anon"); // api filterMap.put("/api/**", "anon"); // 非匿名策略 filterMap.put("/**", "authc"); filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory; }/** * - 注册shiro方言,让 thymeleaf 支持 shiro 标签 */ @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); }@Bean(name="lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }/** * DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; }/** * - 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }}

    如果需要加入rememberMeCookie /** * -- Remember Me Cookie */ @Beanpublic SimpleCookie rememberMeCookie() {//这个参数是 cookie 的名称SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //如果 httyOnly 设置为 true,则客户端不会暴露给客户端脚本代码,//使用 HttpOnly cookie 有助于减少某些类型的跨站点脚本攻击;simpleCookie.setHttpOnly(true); //记住我 cookie 生效时间,单位是秒simpleCookie.setMaxAge(1 * 24 * 60 * 60); return simpleCookie; }/** * -- 管理器 */@Bean public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g=="); cookieRememberMeManager.setCipherService(new AesCipherService()); cookieRememberMeManager.setCipherKey(cipherKey); return cookieRememberMeManager; }/*** sessionCookie*/@Bean public SimpleCookie sessionCookie() {SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion"); simpleCookie.setPath("/"); simpleCookie.setHttpOnly(true); simpleCookie.setMaxAge(1 * 24 * 60 * 60); return simpleCookie; } 在DefaultWebSessionManager 加入 /** * DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 Shiro 默认 Cookie 名称是 JSESSIONID,与 Tomcat 等默认JSESSIONID 冲突,我们需要为 Shiro 指定一个不同名称的 Session id,否则抛出 UnknownSessionException: There is no session with id 异常 */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); sessionManager.setSessionIdCookie(sessionCookie()); advisorAutoProxyCreator.setProxyTargetClass(true); // 相隔多久检查一次 session 的有效性 // sessionManager.setSessionValidationInterval(1 * 24 * 60 * 60 * 1000); // session 有效时间 // sessionManager.setGlobalSessionTimeout(1 * 24 * 60 * 60 * 1000); return advisorAutoProxyCreator; }在SecurityManager 加入 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setRememberMeManager(rememberMeManager()); securityManager.setSessionManager(sessionManager()); return securityManager; }

    身份验证
    UserServiceImpl
    @Override public Result login(User user) {// 得到 subject Subject subject = SecurityUtils.getSubject(); // 封装一个登录令牌(装载用户名和密码) UsernamePasswordToken token = new UsernamePasswordToken( user.getUserName(), MD5Util.encode(user.getPassword())); try { // subject.login() subject.login(token); subject.checkRoles(); // 获取当前用户,并将之设置到 session 中 User temp = (User) subject.getPrincipal(); Session session = subject.getSession(); session.setAttribute("user", temp); return new Result(Result.ResultStatus.SUCCESS.code, "Success", temp); } catch (Exception e) { e.printStackTrace(); LOGGER.debug(e.getMessage()); return new Result(Result.ResultStatus.FAILD.code, e.getMessage()); }}

    Role 配合页面标签,控制不同用户访问不同页面
    /** * 127.0.0.1/account/users---- get */ @RequiresRoles(value = https://www.it610.com/article/{"admin","manager"},logical = Logical.OR) @GetMapping(value = "https://www.it610.com/account/users") public String userPage(ModelMap modelMap) { modelMap.addAttribute("template", "account/users"); return "index"; }

    Resource 配合控制器注解,控制某个特殊的授权权限
    @RequiresPermissions("/api/user/{id}") public Result deleteUserById(@PathVariable int id){ return userService.deleteUserById(id); }
    Html页面

    #|Shiro学习与笔记
    文章图片

    部分转载于http://www.sfac.xyz:8000/notes/Java/Apache_Shiro.html

      推荐阅读