Shiro简介
Shiro是Apache提供的一个功能强大且易于使用的Java安全框架,主要用于:认证(Authentication)、鉴权(Authorization)、加密(Cryptography)、会话管理(Session Management)
Shiro核心组件
Shiro运行过程中有三大核心组件:Subject、SecurityManager、Realm
Subject
个体、事务、主体,Subject可以调用SecurityManager的绝大多数方法,相当于是个传话者
SecurityManager
负责调配资源、调度各种各样组件完成业务的实现。SecurityManager内部非常复杂,当我们想要使用SecurityManager实现某些功能时只需要直接调用Shiro提供的简化的方法(将SecurityManager交给SecurityUtils托管,通过SecurityUtils获取Subject去操作SecurityManager,Subject可以调用SecurityManager的绝大多数方法,相当于是个传话者),相当于是个大哥,负责主要的核心业务
Realm
为Shiro业务的实现提供数据:权限数据、角色数据、用户数据;相当于DAO层(持久层)
RBAC模型
Role Base Access Controller基于角色的访问控制,模型中有三个主体:用户、角色、权限
权限访问控制做的事:
1、身份校验:判断用户是否为合法用户
2、权限校验:用户要做某事、使用某些资源,必须要拥有某角色或某权限,判断用户是否拥有相关的角色权限
配置shiro.ini为realm提供数据支持
[users]
# 一个[]表示一个区域,区域与区域之间以[]识别
# 配置用户信息
# 用户名=密码,角色
root=123,admin
zhangsan=456,userManager
[roles]
# 配置角色信息
# 角色名=权限1,权限2
admin=*
userManager=user:insert,user:query,user:delete,user:update
deptManager=dept:insert,dept:query,dept:delete,dept:update
[main]
# 配置校验失败跳转的地址
# 没有身份认证时,跳转地址(以重定向的方式跳转)
shiro.loginUrl = /user/login/page
# ⻆⾊或权限校验不通过时,跳转地址(以重定向的方式跳转)
shiro.unauthorizedUrl=/author/error
# 登出后的跳转地址,回⾸⻚(以重定向的方式跳转)
shiro.redirectUrl=/user/login/page
[urls]
# 配置访问接口所需要的角色权限
# 接口路径=是否需要认证,roles["admin","user"],perms["user:insert","user:query"]
# anon => 不需要身份认证
# authc => 需要身份认证
user/login=anon
user/insert=authc,roles["admin"]
user/query=authc,prems["user:query"]
public static void main(String[] args) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager manager = factory.getInstance();
SecurityUtils.setSecurityManager(manager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "456");
// shiro为什么能判断密码正确与否?
// 是因为Realm为SecurityManager提供了数据支持,SecurityManager将令牌中的账户秘密跟Realm中的账号和密码做比对
// 通过IniSecurityManagerFactory这种方式获取到的SecurityManager,会将所有的用户、角色、权限信息都读取到IniRealm中,在校验的时候会将token中的用户名和密码和从Reaml中的用户和名密码进行比对
// users是一个LinkedHashMap,roles是一个LinkedHashMap
subject.login(token);
System.out.println(subject.isAuthenticated());
System.out.println(subject.hasRole("user:*"));
subject.login(token);
}
<!--
配置web.xml
-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>Archetype Created Web Application</display-name>
<!--
初始化SpringMVC的环境,创建DispatcherServlet
-->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--
1. 在项目的最外层 构建访问控制层
2. 在启动时,初始化shiro环境
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 在项目启动时,加载web-info 或 classpath下的 shiro.ini ,并构建WebSecurityManager。
构建所有配置中使用的过滤器链(anon,authc等),ShiroFilter会获取此过滤器链
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!--
初始化Spring容器的环境
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
ShiroFilter原理
Shior的认证、鉴权是通过一系列的过滤器链完成的,ShiroFilter相当于一层外壳,囊括着所有的过滤器,拦截所有的请求。
过滤器 | 过滤器类 | 描述 | 例子 |
---|---|---|---|
anon | AnonymouseFilter | 表示不需要身份认证,可以匿名访问 | user/login=anon |
authc | FormAuthenticationFilter | 表示需要登录才能访问 | user/query=authc |
自定义Realm
// shiro在认证的过程中会先调用Realm获取用户信息、角色信息、权限信息,realm会在shiroFilter执行,
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("Shiro正在获取角色权限信息");
String userName = (String) principalCollection.getPrimaryPrincipal();
RoleServiceImpl roleServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("roleServiceImpl", RoleServiceImpl.class);
PermissionServiceImpl permissionServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("permissionServiceImpl", PermissionServiceImpl.class);
// 获取角色信息
Set<String> roleNames = roleServiceImpl.getRoleNameByUserName(userName);
// 获取权限信息
Set<String> permissionNames = permissionServiceImpl.getPermissionNameByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roleNames);
simpleAuthorizationInfo.setStringPermissions(permissionNames);
return simpleAuthorizationInfo;
}
/**
* 作用:查询身份信息,返回即可,不用做任何比对,交给AuthenticationFilter进行比对
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("Shiro正在获取用户信息");
String userName = (String) authenticationToken.getPrincipal();
UserServiceImpl userServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("userServiceImpl", UserServiceImpl.class);
User currentUser = userServiceImpl.getUserByUserName(userName);
// 到时候AuthenticationFilter会拿simpleAuthenticationInfo里面的password和用户输入的password做比对
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(currentUser.getUserName(), currentUser.getPassword(), this.getName());
return simpleAuthenticationInfo;
}
}
在shiro.ini文件的[main]区域添加如下内容,并删除[users]和[roles]两个区域的内容,[users]和[roles]中的数据将由自定义的Realm提供
#将自定义的realm和Shiro绑定,告知Shiro不再使用自定义的Realm
# userRealm是自定义名称
userRealm=com.XXX.XXX.XXX.MyRealm
securityManager.realms=$userRealm
配置密码加密\校验策略
加密存储策略:
@PostMapping("/add")
public void userAdd(User user){
String salt = UUID.randomUUID().toString();
//Sha256加密算法转base64进行存储
String newPassword1 = new Sha256Hash(user.getPassword(),salt,1000).toBase64();
//Sha256加密算法转16进制进行存储
//String newPassword2 = new Sha256Hash(user.getPassword(),salt,1000).toHex();
//转base64进行存储
//String newPassword3 = new Md5Hash(user.getPassword(),salt,1000).toBase64();
//转16进制进行存储
//String newPassword4 = new Md5Hash(user.getPassword(),salt,1000).toHex();
user.setSalt(salt);
user.setPassword(newPassword1);
userService.addUser();
}
校验策略:
第一步:在shiro.ini文件的[main]区域添加如下内容:
# 指定密码比对器======================================================
# passwordMatcher是自定义名称
passwordMatcher=org.apache.shiro.authc.credential.SimpleCredentialsMatcher
# 指定加密算法
passwordMatcher.hashAlgorithmName=sha-256
# 指定迭代次数
passwordMatcher.hashIterations=10000
# 指定加密结果的编码方式:true转换为十六进制格式Hex,false转换为Base64格式
passwordMatcher.storedCredentialsHexEncoded=false
# 指定Realm=========================================================
#将自定义的realm和Shiro绑定,告知Shiro不再使用自定义的Realm
# userRealm是自定义名称
userRealm=com.XXX.XXX.XXX.MyRealm
# 通过Realm中告知SecurityMannager使用的密码比对器
userRealm.credentialsMatcher=$passwordMatcher
# 告知SecurityMannager使用的Realm
securityManager.realms=$userRealm
Spring-Shiro
导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
在容器内注入DefaultWebSecurityManager、ShiroFilter工厂ShiroFilterFactoryBean、密码匹配器HashedCredentialsMatcher、自己定义的Realm
<!--
dtd : document type definition xxx.dtd xxx2.dtd
xsd : xml schema definition xxx.xsd xxx2.xsd
文档约束:标签,顺序,层级,属性
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- shiro配置 -->
<!-- Realm -->
<bean id="myRealm" class="com.XXX.realm.MyRealm">
<property name="userService" ref="userServiceImpl"/>
<property name="roleService" ref="roleServiceImpl"/>
<property name="permissionService" ref="permissionServiceImpl"/>
<!-- 密码比对器 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
<!-- true means hex encoded, false means base64 encoded -->
<property name="storedCredentialsHexEncoded" value="false"/>
<property name="hashIterations" value="10000"/>
</bean>
</property>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<!-- 记住我管理器 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
<!-- session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- shiroFilter -->
<!-- 生产SpringShiroFilter
( 持有shiro的过滤相关规则,可进行请求的过滤校验,校验请求是否合法 )
-->
<bean id="shiroFilter04" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入核心对象 -->
<property name="securityManager" ref="securityManager"/>
<!-- 未登录,没权限时的跳转路径 -->
<!-- <property name="loginUrl" value="/user/login"/>
<property name="unauthorizedUrl" value="/user/perms/error"/> -->
<!-- 过滤器链 -->
<!-- <property name="filterChainDefinitions">
<value>
/user/all=authc,roles["banzhang"]
/user/logout=logout
<!–/user/insert=authc,roles["banfu"]
/user/update=authc,perms[""student:update""]
/order/insert=authc,roles["xuewei"]
–>
</value>
</property> -->
</bean>
<!-- 记住我的Cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- rememberMe是cookie值中的key,value时用户名的密文
cookie["rememberMe":"deleteMe"] 此cookie每次登陆后都会写出,用于清除之前的cookie
cookie["rememberMe":username的密文] 此cookie也会在登录后写出,用于记录最新的username
(ops: 如上设计,既能保证每次登陆后重新记录cookie,也能保证切换账号时,记录最新账号)
-->
<property name="name" value="rememberMe04"/>
<!-- cookie只在http请求中可用,那么通过js脚本将无法读取到cookie信息,有效防止cookie被窃取 -->
<property name="httpOnly" value="true"/>
<!-- cookie的生命周期,单位:秒 -->
<property name="maxAge" value="604800"/>
<!-- 7天 -->
</bean>
<!-- 记住我管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 会话Cookie模板 默认可省 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的 key="sid" -->
<property name="name" value="JSESSIONID04"/>
<!-- 只允许http请求访问cookie -->
<property name="httpOnly" value="true"/>
<!-- cookie过期时间,-1:存活一个会话 ,单位:秒 ,默认为-1 -->
<property name="maxAge" value="-1"/>
</bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 默认值和配置中给出的一致,所bean:sessionIdCookie 可以省略 -->
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<!-- session全局超时时间, 单位:毫秒 ,30分钟 默认值为1800000 -->
<property name="globalSessionTimeout" value="1800000"/>
<!-- 注册session监听器 -->
<property name="sessionListeners">
<list>
<bean class="com.XXX.session.MySessionListener"/>
</list>
</property>
<!-- session检测的时间间隔 -->
<property name="sessionValidationInterval" value="15000"/>
</bean>
</beans>
其他相关的配置
<!--
dtd : document type definition xxx.dtd xxx2.dtd
xsd : xml schema definition xxx.xsd xxx2.xsd
文档约束:标签,顺序,层级,属性
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入外部参数文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 连接池:druid -->
<bean id="druid" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="${jdbc.maxPoolSize}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="3000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小空闲的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
</bean>
<!-- SqlSessionFactory
配置: 1.连接池
2.mapper文件信息
3.别名-可选
4.插件-可选
-->
<bean id="sqlSessionFactory04" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- <property name="configLocation" value="classpath:configuration.xml"/> -->
<!-- 连接池 -->
<property name="dataSource" ref="druid"/>
<!-- mapper文件
如果和dao接口在同包且同名,则如下注入可以省略
-->
<!-- <property name="mapperLocations" value="classpath:com/XXX/dao/*.xml"/> -->
<!-- 别名 -->
<property name="typeAliasesPackage" value="com.XXX.pojo"/>
</bean>
<!-- 扫描所有mapper,为每个dao定制实现类 sqlSession.getMapper(UserDAO.class)
DAO实现的对象会 纳入工厂,并且beanID=DAO的首字母小写接口类名=“userDAO”
1> DAO接口
2> DAO映射文件
3> SqlSession
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 如果当前工厂中 只有一个SqlSessionFactory,则此项注入可以省略 -->
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory04"/> -->
<!-- DAO接口位置 -->
<!-- <property name="basePackage" value="com.XXX.dao,com.XXX2.dao2"/> -->
<property name="basePackage" value="com.XXX.dao"/>
</bean>
<!-- 事务管理 -->
<!-- 事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druid"/>
</bean>
<!-- 支持 @Transactional 实现事务 -->
<tx:annotation-driven transaction-manager="tx"/>
<!-- 配置注解扫描:让spring去发现注解,进而实现对应的功能 -->
<context:component-scan base-package="com.XXX">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<import resource="classpath:shiro_applicationContext.xml"/>
</beans>
配置web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>Archetype Created Web Application</display-name>
<!-- 前端控制器 -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring启动配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--
DelegatingFilterProxy是filter的代理类,它会内部通过this.getFilterName()方法获取到自己的名字,并将自己的名字作为被代理对象的名字,
然后从spring工厂中获取和它同名的bean,(id="shiroFilter"),接到请求后调用bean的doFilter方法,进行访问控制。
如果不指定名称,则默认名称为delegatingFilterProxy
-->
<filter>
<filter-name>shiroFilter04</filter-name> <!--指定filter的名字-->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter04</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Shiro整合SpringBoot
核心代码
package org.example.shiro.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.example.shiro.realm.MyRealm;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
@Configuration
public class ShiroConfig {
//密码认证器
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("sha-256");
hashedCredentialsMatcher.setHashIterations(10000);
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
return hashedCredentialsMatcher;
}
//Realm
@Bean
public AuthorizingRealm getAuthorizingRealm(HashedCredentialsMatcher matcher){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}
//SecurityManager
@Bean
public SecurityManager getSecurityManager(AuthorizingRealm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin","anon");
definition.addPathDefinition("/myController/Login","anon");
definition.addPathDefinition("/login","anon");
//设置需要进行登录认证的拦截范围
definition.addPathDefinition("/**","authc");
return definition;
}
//ShiroFilter
@Bean("shiroFilter")
public ShiroFilterFactoryBean getShiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("");
shiroFilterFactoryBean.setUnauthorizedUrl("/user/login");
shiroFilterFactoryBean.setFilterChainDefinitions("");
return shiroFilterFactoryBean;
}
//Filter代理类,注册到过滤器链上
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> getDelegatingFilterProxy(){
FilterRegistrationBean<DelegatingFilterProxy> filterFilterRegistrationBean = new FilterRegistrationBean<>();
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
delegatingFilterProxy.setTargetBeanName("shiroFilter");
delegatingFilterProxy.setTargetFilterLifecycle(true);
filterFilterRegistrationBean.setFilter(delegatingFilterProxy);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
}
package org.example.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.apache.shiro.util.ByteSource;
import org.example.shiro.pojo.User;
import org.example.shiro.service.PermissionServiceImpl;
import org.example.shiro.service.RoleServiceImpl;
import org.example.shiro.service.UserServiceImpl;
import org.springframework.web.context.ContextLoader;
import java.util.Set;
// shiro在认证的过程中会先调用Realm获取用户信息、角色信息、权限信息,realm会在shiroFilter执行,
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("Shiro正在获取角色权限信息");
String userName = (String) principalCollection.getPrimaryPrincipal();
RoleServiceImpl roleServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("roleServiceImpl", RoleServiceImpl.class);
PermissionServiceImpl permissionServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("permissionServiceImpl", PermissionServiceImpl.class);
// 获取角色信息
Set<String> roleNames = roleServiceImpl.getRoleNameByUserName(userName);
// 获取权限信息
Set<String> permissionNames = permissionServiceImpl.getPermissionNameByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roleNames);
simpleAuthorizationInfo.setStringPermissions(permissionNames);
return simpleAuthorizationInfo;
}
/**
* 作用:查询身份信息,返回即可,不用做任何比对,交给AuthenticationFilter进行比对
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("Shiro正在获取用户信息");
String userName = (String) authenticationToken.getPrincipal();
UserServiceImpl userServiceImpl = ContextLoader.getCurrentWebApplicationContext().getBean("userServiceImpl", UserServiceImpl.class);
User currentUser = userServiceImpl.getUserByUserName(userName);
// 到时候AuthenticationFilter会拿simpleAuthenticationInfo里面的password和用户输入的password做比对
//SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(currentUser.getUserName(), currentUser.getPassword(), this.getName());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(currentUser.getUserName(), currentUser.getPassword(), ByteSource.Util.bytes(currentUser.getSalt()), this.getName());
return simpleAuthenticationInfo;
}
}