Shiro
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注解
- @RequiresAuthentication : 表示当前 Subject 需要登录;
- @RequiresUser : 表示当前 Subject 需要登录或记住我;
- @RequiresGuest : 表示当前 Subject 是游客身份;
- @RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):当前 Subject 需要的角色;
- @RequiresPermissions (value={““,””}, logical= Logical.OR) :当前 Subject 需要拥有的资源;
既可以用在controller中,也可以用在service中建议将shiro注解放入controller,因为如果service层使用了spring的事物注解,那么shiro注解将无效
shiro的优点
1、 简单的身份验证,支持多种数据源
2、对角色的简单授权,支持细粒度的授权(方法)
3、支持一级缓存,以提升应用程序的性能
4、内置基于POJO的企业会话管理,适用于web及非web环境
5、非常简单的API加密
6、不跟任何框架绑定,可以独立运行
运行流程
- 首先调用
Subject.login(token)
进行登录,其会自动委托给Security Manager
,调用之前必须通过SecurityUtils.setSecurityManager()
设置; SecurityManager
负责真正的身份验证逻辑;它会委托给Authenticator
进行身份验证;Authenticator
才是真正的身份验证者,Shiro API
中核心的身份认证入口点,此处可以自定义插入自己的实现;Authenticator
可能会委托给相应的AuthenticationStrategy
进行多 Realm 身份验证,默认ModularRealmAuthenticator
会调用AuthenticationStrategy
进行多 Realm 身份验证;Authenticator
会把相应的token
传入Realm
,从Realm
获取身份验证信息,如果没有返回 则抛出异常表示身份验证失败了。此处可以配置多个Realm
,将按照相应的顺序及策略进行访问。
前端标签
jsp页面
首先要导入标签库;
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>:导入标签库;
<shiro:guest></shiro:guest>:游客访问;
<shiro:user></shiro:user>:用户需要登录或记住我;
<shiro:authenticated></shiro:authenticated>:用户需要登录;
<shiro:hasRole name="admin"></shiro:hasRole>:用户需要拥有某种角色访问;
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>:用户需要拥有某些角色;
<shiro:hasPermission name="user:create"></shiro:hasPermission>:用户需要拥有某资源;进行显示
Thymeleaf
导入
<html
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
>
</html>
shiro:guest:游客访问;
shiro:user:用户需要登录或记住我;
shiro:authenticated:用户需要登录;
shiro:hasRole:用户需要某角色;
shiro:hasAnyRoles:用户需要某些角色;
shiro:hasPermission:用户需要某权限;
-----------------------------------------------------------
<li shiro:hasAnyRoles="admin,manager">:用户需要 admin 或 manager 角色可访问;
<li><span shiro:principal/></li>:身份验证器包装的用户名时,页面获取用户名;
<span shiro:principal property="loginName"/>:身份验证器中包装 user 对象时,页面获取用户名;
<input type="hidden" id="userId" th:value="${session.user.userId}">:页面获取 session 中 user 对象;
在spring框架中集成shiro
Pom文件进行依赖配置
可以在maven仓库包https://mvnrepository.com/,根据三要素进行查找
<!-- shiro -->
<properties>
<shiro.version>1.7.1</shiro.version>
</properties>
<!-- shiro配置 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 支持Spring框架集成-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 支持基于web的应用程序 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 启用对Shiro AOP和注释的AspectJ支持。 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- 启用基于ehcache的框架缓存。 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
项目跑起来了,只是控制台输出了
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作为底层的日志实现框架。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
集成Shiro
在Spring框架中集成Shiro,本质上是与Spring IoC容器和Spring MVC框架集成.
web.xml中的设置
<!-- shiro过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<!--官方解释到,支持targetBeanName 通过 init-param 这个节点来设置-->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
shiro的bean配置文件,
其中包含过滤设置。此文件名为spring-shiro.xml,里面定义了需要的Bean,完成诸多功能。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<!-- 这些类注册为spring容器中的bean-->
<context:component-scan base-package="com.qq"/>
<!--注入自定义的Realm-->
<bean id="userRealm" class="com.qq.realm.UserRealm"/>
<!--开启shiro的注解-->
<bean id="advisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<!--必须 否则shiro就无法解析权限url-->
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- Shiro 生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 -->
<!--让RequiredPermissions生效-->
<!-- 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/> <!-- 这句可能要打开才能启用注解 -->
</bean>
<!-- Security Manager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 定义了认证逻辑和授权逻辑-->
<property name="realm" ref="userRealm"/>
</bean>
<!-- Shiro的Web过滤器 id 必须 和web.xml文件中配置的一致-->
<!--配置ShiroFilter-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的安全管理器,所有关于安全的操作都会经过SecurityManager -->
<property name="securityManager" ref="securityManager"/>
<!-- 认证页面:未认证用户请求需要认证的url时就会自动跳转到此页面url。-->
<!-- 它不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的"/login.jsp"。-->
<property name="loginUrl" value="/login/view"/>
<!-- 没有授权的用户请求需要授权的url时就会被跳转到这个地址 --> <!-- 但是我发现这句不起作用 -->
<property name="unauthorizedUrl" value="/login/view" />
<!-- 认证成功页面url:-->
<!-- 理想状况下,它是认证成功后默认跳转路径,不设置则跳转至"/"。-->
<!-- 理想状况下,如果点击一个需认证的url,经认证后则会自动跳转到之前这个需要认证的url,而不跳到successUrl。-->
<!-- 但上面所说的仅是理想状况下 successUrl的功效。实验时往往差强人意-->
<property name="successUrl" value="/home"/>
<!--
<property name="filters">
<map>
<entry key="logout" value-ref="logoutFilter" />
</map>
</property>
-->
<!-- URL的过滤规则指定-->
<!-- 用得最多的是
anon(匿名可访问),
authc(认证后可访问),
logout(注销时用)
(会清除所有sesseion和一些cookie)(会默认跳往根目录)
user:登录过能访问
roles:角色过滤器
-->
<property name="filterChainDefinitions" >
<value>
/login/view = anon
/back/student/zzz =authc,roles[admin]
/back/** = authc
</value>
</property>
</beans>
或者:springShiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd"
default-lazy-init="true">
<!-- Realm 配置 -->
<bean id="myRealm" class="com.sfac.springMvc.config.MyRealm" />
<!-- Remember Me 配置 -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<property name="name" value="rememberMe"/>
<property name="maxAge" value="604800"/>
<property name="httpOnly" value="true"/>
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- Session 配置 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- Session 的失效时长,单位毫秒 -->
<property name="globalSessionTimeout" value="6000000"/>
<!-- 删除失效的 Session -->
<property name="deleteInvalidSessions" value="true"/>
<!-- 去掉重定向后 Url 追加 Session Id -->
<property name="sessionIdUrlRewritingEnabled" value="false" />
</bean>
<!-- Security Manager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<property name="rememberMeManager" ref="rememberMeManager"></property>
<property name="sessionManager" ref="sessionManager" />
</bean>
<!--
Shiro Filter
anon:匿名访问,无需登录
authc:登录后才能访问
user:登录过能访问
logout:登出
roles:角色过滤器
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/common/dashboard"/>
<property name="filterChainDefinitions">
<value>
/static/**=anon
/register=anon
/login=anon
/forgot=anon
/logout=logout
/api/**=anon
/**=authc
</value>
</property>
</bean>
<!-- 配置验证表单名称,默认是:userName,password -->
<!-- <bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="account"></property>
<property name="passwordParam" value="password"></property>
</bean> -->
<!-- Shiro 生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- DefaultAdvisorAutoProxyCreator, Advisor 代理类生成器 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<!-- 创建 AuthorizationAttributeSourceAdvisor,扫描 Shiro 注解 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
</beans>
必须是在MVC配置文件中用
<import resource=*"spring-shiro.xml"*></import>
将spring-shiro.xml载入。
例子:
-
身份验证
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 { return new 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")){ // // return new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),this.getName()); // // }else { // throw new IncorrectCredentialsException("用户不存在"); // } // // } } }
或者:MyRealm.java
public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private ResourceService resourceService; /** * -资源授权器 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 授权类 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principals.getPrimaryPrincipal(); if (user == null) { return null; } List<Role> roles = Optional .ofNullable(roleService.getRolesByUserId(user.getId())) .orElse(Collections.emptyList()); roles.stream().forEach(role -> { authorizationInfo.addRole(role.getRoleName()); List<Resource> resources = Optional .ofNullable(resourceService.getResourcesByRoleId(role.getId())) .orElse(Collections.emptyList()); resources.stream().forEach(resource -> { authorizationInfo.addStringPermission(resource.getPermission()); }); }); return authorizationInfo; } /** * -身份验证器 */ @Override protected 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<User> login(User user);
UserServiceImpl
```java
@Service
public class UserServiceImpl implements UserService{
@Override
public ResultEntity<User> login(User user) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),
MD5Util.getMD5(user.getPassword()));
token.setRememberMe(user.getRememberMe());
return new Result<User>(Result.ResultStatus.SUCCESS.code, "Success", user);
try {
subject.login(token);
subject.checkRoles();
} catch (Exception e) {
e.printStackTrace();
return new Result<User>(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文件
<!-- shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- thymeleaf 对 shiro 的扩展包,支持页面书写 shiro 标签 -->
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf shiro 根据权限标签显示-->
<!--shiro与themeleaf集成-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
在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 MyRealm extends 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<Role> roles = roleService.getRolesByUserId(user.getId());
roles.stream().forEach(item -> {
authorization.addRole(item.getRoleName());
// authorization.addStringPermission(item.getRoleName());
// 再去查询每个角色拥有的资源列表,并装载到资源授权器里
List<Resource> 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<String, String> 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
*/
@Bean
public 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<User> 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<User>(Result.ResultStatus.SUCCESS.code, "Success", temp);
} catch (Exception e) {
e.printStackTrace();
LOGGER.debug(e.getMessage());
return new Result<User>(Result.ResultStatus.FAILD.code, e.getMessage());
}
}
Role 配合页面标签,控制不同用户访问不同页面
/**
* 127.0.0.1/account/users---- get
*/
@RequiresRoles(value = {"admin","manager"},logical = Logical.OR)
@GetMapping(value = "/account/users")
public String userPage(ModelMap modelMap) {
modelMap.addAttribute("template",
"account/users");
return "index";
}
Resource 配合控制器注解,控制某个特殊的授权权限
@RequiresPermissions("/api/user/{id}")
public Result<Object> deleteUserById(@PathVariable int id){
return userService.deleteUserById(id);
}
Html页面
<ul class="nav child_menu">
<li shiro:hasAnyRoles="admin,manager,staff"><a href="javascript:void(0);">Profile</a></li>
<li shiro:hasAnyRoles="admin,manager"><a href="/account/users">Users</a></li>
<li shiro:hasAnyRoles="admin,manager"><a href="/account/roles">Roles</a></li>
<li shiro:hasAnyRoles="admin,manager"><a href="/account/resources">Resources</a></li>
</ul>
<ul class="nav child_menu">
<shiro:hasPermission name="/test/thymeleafTest">
<li shiro:hasAnyRoles="admin,manager,staff">
<a href="/test/thymeleafTest">ThymeleafTest</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="/test/vueTest">://用户需要拥有某资源;进行显示
<li shiro:hasAnyRoles="admin,manager,staff"><a href="/test/vueTest">VueTest</a></li>
</shiro:hasPermission>
<li shiro:hasAnyRoles="admin,manager,staff">
<a>二级菜单<span class="fa fa-chevron-down"></span></a>
<ul class="nav child_menu">
<li class="sub_menu"><a href="javascript:void(0);">三级菜单一</a></li>
<li><a href="javascript:void(0);">三级菜单二</a></li>
</ul>
</li>
</ul>
部分转载于http://www.sfac.xyz:8000/notes/Java/Apache_Shiro.html