注意:所有文章除特别说明外,转载请注明出处.
概念
Shiro是简洁易用的安全框架包括(Subject,securityManager,Realms)。权限管理包括:用户认证、用户授权。
1.Subject
这里表示用户,表示Shiro可以表示一个实际的用户,也可以是第三方的服务,系统,或者Job等。在用户认证过程中,可以将subject主体理解为用户或者程序,要去访问系统的资源,系统需要对subject进行身份认证。
注意:每个Subject实例都会绑定对应的SecurityManager。
2.SecurityManager
是整个Shiro的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。是安全管理器,主体进行认证和授权都是通过securityManager进行的。
2.1.authenticator
认证器,主体进行认证最终通过authenticator进行的。
2.2.authorizer
授权器,主体进行授权最终通过authorizer进行的。
2.3.sessionManager
web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
2.4.SessionDao
通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
2.5.cache Manager
缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
2.6.realms
域,领域,相当于数据源,通过realm存取认证、授权相关数据。
2.6.1.Realms
是连接Shiro和应用程序安全数据之间的桥梁。当进行鉴权和授权的时候,Shiro会查询一个或者多个Realms。Shiro提供一些默认的Realm,例如LDAP, JDBC和INI文件中。
2.7.cryptography
密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。MD5散列算法。
详细架构
1.使用用户的登录信息创建令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
注:token理解为用户令牌,登录的过程被抽象为shiro验证令牌是否具有合法身份以及相关权限。
2.执行登录动作
SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
Subject subject = SecurityUtils.getSubject(); // 获取Subject单例对象
subject.login(token); // 登陆
注:SecurityManager负责安全认证与授权。SecurityUtils对象本质上类似spring中的applicationContext。
3.判断用户
因为Shiro本身无法知道所持有令牌的合法性,所以Realm需要设计者自行实现。最重要的一种实现方式,数据库查询。
4.AuthenticationInfo代表了用户的角色信息集合,AuthorizationInfo代表了角色的权限信息集合
实现Realm
Shiro认证
1.导jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.3</version>
</dependency>
提示:或者直接导入shiro相关的全部jar包。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
2.认证流程
1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
2.Subject.login() 提交认证 (主体提交认证请求)
//0.1这里为了简单,使用SimpleAccountRealm创建简单的realm对象
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
//0.2在执行之前给realm添加一个用户
@Before
public void addUser(){
simpleAccountRealm.addAccount("Brian","Brian_3365");
}
//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//1.2将simpleAccountRealm设置到环境中来
defaultSecurityManager.setRealm(simpleAccountRealm);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
3.SecurityManager.login() 执行认证
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//提交认证
subject.login(token);
4.Authenticator 执行认证
//4.执行得到 是否已经得到认证
subject.isAuthenticated();
5.Realm 根据身份获取验证信息
//这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());
//5.在成功登陆之后,然后subject提供了一个登出的方法
subject.logout();
Shiro 授权
1.构建SecurityManager环境
2.主体授权
3.SecurityManager授权
4.Authorizer授权
5.Realm 获取角色权限数据
//0.1这里为了简单,使用SimpleAccountRealm创建简单的realm对象
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
//0.2在执行之前给realm添加一个用户并设置用户角色(admin)
@Before
public void addUser(){
simpleAccountRealm.addAccount("Brian","Brian_3365","admin");
}
//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//1.2将simpleAccountRealm设置到环境中来
defaultSecurityManager.setRealm(simpleAccountRealm);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//提交认证
subject.login(token);
//4.执行得到 是否已经得到认证
subject.isAuthenticated();
//这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());
//检查用户角色
subject.checkRole("admin");
//5.在成功登陆之后,然后subject提供了一个登出的方法
subject.logout();
Shiro 自定义Realm
1.内置Realm
1.1 IniRealm
验证:
//0.1 创建IniRealm对象
IniRealm iniRealm = new IniRealm("classpath:user.ini");
//0.2 然后在对应的路径下新建user.ini文件 (用户名 = 密码)
[users]
Aaron = Aaron_2235
//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//1.2将simpleAccountRealm设置到环境中来
defaultSecurityManager.setRealm(simpleAccountRealm);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//提交认证
subject.login(token);
//这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());
授权:
//对应路径下创建user.ini文件
[users]
Aaron = Aaron_2235,admin
[roles]
//管理员用户删除用户的权限
admin = user:delete
//然后在 验证 的代码之后由命令检验角色信息
subject.checkRole("admin");
//以及验证是否具备删除用户权限
subject.checkPermission("user:delete");
1.2 JdbcRealm
//在最开始,需要创建一个数据源
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
}
//0.1 创建JdbcRealm对象 其有默认的查询语句
JdbcRealm jdbcRealm = new JdbcRealm();
//0.2 jdbcRealm需要访问数据库,所以需要加入依赖
//0.3 创建jdbc的数据源
jdbcRealm.setDataSource(dataSource);
//在使用jdbcRealm权限查询的时候,需要设置权限开关
jdbcRealm.setPermissionLookupEnable(true);
//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//1.2 将jdbcRealm设置到环境中来
defaultSecurityManager.setRealm(jdbcRealm);
//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//4.这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());
2.自定义Realm(安全数据源)
Shiro从Realm域获取安全数据(如用户、角色、权限),所以SecurityManager需要验证身份,那么就需要从Realm中获取相应的用户进行比较来确定用户身份是否合法,同时也需要从Realm中得到用户相应的角色、权限进行验证用户是否能够进行操作。
public class CustomRealm extends AuthorizingRealm{
//此方法是用来做授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户
String username = (String)PrincipalCollection.getPrincipalCollection();
//从数据库或者缓存中获取角色数据,数据库操作
Set<String> roles = getRolesByUserName(username);
Set<String> permissions = getPermissionsByUserName(username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//向其中加入相应的角色和权限
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
//开始授权操作
return simpleAuthorizationInfo;
}
private Set<String> getRolesByUserName(String username){
Set<String> sets = new HashSet<>();
sets.add("admin");
sets.add("user");
return sets;
}
private Set<String> getPermissionsByUserName(String username){
Set<String> sets = new HashSet<>();
sets.add("user:delete");
sets.add("user:select");
return sets;
}
//此方法是用来做认证
//AuthenticationToken 主体传过来的认证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//这里从简(数据库)
Map<String, String> userMap = new HashMap<>();
{
//userMap.put("Aaron","Aaron_2254");
//Shiro加密实验部分
userMap.put("Aaron","md5加密后的密文");
//Shiro加密加盐
userMap.put("Aaron","md5加密加盐后密文");
super.setName("cusRealm");
}
//1.从主体传递的认证信息中获取用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//2.通过用户名去到数据库中获取凭证
String password = getPasswordByUserName(username);
if(password == null ){
return null;
}
//用户存在 返回对象simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("Aaron",password,"cusRealm");
//这里登录的主要信息:实体类对象,但是该实体类对象一定是根据token的username查询得到的
//在这里需要将盐设置到simpleAuthenticationInfo
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("Aaron"));
return simpleAuthenticationInfo;
}
//2.1 模拟数据库
private String getPasswordByUserName(String username){
return userMap.get(username);
}
}
//Shiro 加密设置 将明文密码改成密文密码
public static void main(String[] args){
Md5Hash md5Hash = new Md5Hash("Aaron_2254");
System.out.println(md5Hash.toString());
}
//然后在验证信息里面使用加密后的密文
}
测试类:测试上面两个自定义的Realm是否正确。
Shiro在Controller中实现
//这里只截取主要部分
//获取提交主体
Subject subject = SecurityUtils.getSubject();
//根据用户名获取数据库登录信息
UsernamePasswordToken token = new UsernamePasswordToken(uid,"");
try {
//提交登录信息
subject.login(token);
} catch (AuthenticationException e) {
LOG.error(e.getMessage());
//返回403界面
response.sendRedirect("/403.html");
}
...
Shiro加密
1.Shiro散列配置
1.1 HashedCredentialsMatcher
//0.1 自定义Realm,这里的自定义customRealm沿用上面的自定义Realm
CustomRealm customRealm = new CustomRealm();
//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//1.2 将jdbcRealm设置到环境中来
defaultSecurityManager.setRealm(jdbcRealm);
//构建加密算法
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法的名称
matcher.setHashAlgorithmName("md5");
//设置加密的次数
matcher.setHashIterations(1);
//在自定义的Realm中设置加密的matcher对象
customRealm.setCredentialsMatcher(matcher);
//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//4.这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());
1.2 自定义Realm中使用散列
//Shiro 加密设置 将明文密码改成密文密码
public static void main(String[] args){
Md5Hash md5Hash = new Md5Hash("Aaron_2254");
System.out.println(md5Hash.toString());
}
//然后在验证信息里面使用加密后的密文
1.3 盐的使用
//1.加盐表示在MD5加密的基础上加上随机数
public static void main(String[] args){
//加密之后再加上随机数
Md5Hash md5Hash = new Md5Hash("Aaron_2254","Aaron");
System.out.println(md5Hash.toString());
}
//2.然后将生成的随机数加到原先的Realm认证中
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("Aaron",password,"cusRealm");
//在这里需要将盐设置到simpleAuthenticationInfo
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("Aaron"));
Shiro与SpringMVC集成
Shiro实战
0.pom.xml
在原先pom.xml的基础上加上如下的shiro依赖。
<!--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>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
1.配置前端过滤器(web.xml)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Shiro_Project</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置spring容器的路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring-context-*.xml</param-value>
</context-param>
<!-- 对spring开始监听 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置springmvc.xml文件的位置 -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- shiro配置开始,这里注意拦截器的名称 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>shiroFilter</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- shiro配置 结束 -->
</web-app>
注意:由于项目通过Spring管理,所以所有的配置原则上都是交给Spring,那么这里的DelegatingFilterProxy的功能就是通知Spring将所有的Filter叫个ShiroFilter管理。
2.在classpath路径下编写spring-context.xml文件
在此文件下需要配置cacheManager(缓存框架,是因为shiro的session是自己实现的)。
<!-- 缓存bean -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:${ehcache.file}"></property>
</bean>
<!--在pom.xml文件中配置该缓存的依赖-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
3.在classpath路径下编写spring-shiro-web.xml文件 上面文件只是补充
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 1.缓存管理器 使用Ehcache实现 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
</bean>
<!-- 2.加密算法 -->
<bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager" />
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="2" />
<property name="storedCredentialsHexEncoded" value="true" />
</bean>
<!-- Shiro的Web过滤器,启动shiro授权注解拦截方式,这里配置shiro拦截器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--配置SecurityManager对象,此属性非常重要-->
<property name="securityManager" ref="securityManager" />
<!--登录时的链接-->
<property name="loginUrl" value="/" />
<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) -->
<!-- <property name="successUrl" value="/"/> -->
<!--用户访问未授权的资源时所显示的链接-->
<property name="unauthorizedUrl" value="/" />
<!--过滤器链,同时过滤器链是有顺序的,是从上往下匹配,匹配到之后就返回-->
<property name="filterChainDefinitions">
<!--内置的过滤规则-->
<value>
/authc/admin = roles[admin]
<!--此表示需要经过认证之后才能访问相应的路径-->
/authc/** = authc
<!--此表示不需要任何认证-->
/** = anon
</value>
</property>
</bean>
<!-- 创建SecurityManager对象,从而得到自定义用户数据匹配-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--将自定义的Realm对象设置到SecurityManager环境中-->
<property name="realm" ref="userRealm" />
</bean>
<!-- 创建自定义的Realm,来作为授权和认证的realm -->
<bean id="userRealm" class="utils.UserRealm">
<!--以及自定义比对器,比对结果与form提交是否一致-->
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
<!--自定义比对器的bean注入,这一比对器是自定义实现-->
<bean class="cn.edu.xidian.B.authurity.CredentialMatcher" id="credentialMatcher"/>
<!--然后上面过程中的过滤器链可以用程序实现,这样不用每次都去添加一次-->
<bean class="cn.edu.xidian.B.authurity.FilterChainDefinitionMapBuilder" id="filterChainDefinitionMapBuilder"/>
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getMap"/>
<!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>
注意:filterChainDefinitions过滤器中对于路径的配置是有顺序的,当找到匹配的条目之后容器不会在继续寻找。因此带有通配符的路径要放在后面。三条配置的含义是: /authc/admin需要用户有用admin权限、/authc/用户必须登录才能访问、/其他所有路径任何人都可以访问。
4.在classpath路径下配置springmvc.xml文件 没必要配置springmvc.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:p="http://www.springframework.org/schema/p"-->
<!-- xmlns:context="http://www.springframework.org/schema/context"-->
<!-- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"-->
<!-- xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"-->
<!-- xsi:schemaLocation="http://www.springframework.org/schema/beans -->
<!-- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd-->
<!-- http://www.springframework.org/schema/context -->
<!-- http://www.springframework.org/schema/context/spring-context-3.0.xsd-->
<!-- http://www.springframework.org/schema/tx -->
<!-- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd-->
<!-- http://www.springframework.org/schema/aop-->
<!-- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd-->
<!-- http://www.springframework.org/schema/mvc -->
<!-- http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd-->
<!-- http://www.springframework.org/schema/task -->
<!-- http://www.springframework.org/schema/task/spring-task-3.1.xsd">-->
<!-- <context:annotation-config />-->
<!-- 使用注解时必须 -->
<!-- <mvc:annotation-driven>-->
<!-- <mvc:argument-resolvers>-->
<!-- <bean class="cn.edu.xidian.see.ext.DataTableArgumentResolver"/>-->
<!-- </mvc:argument-resolvers>-->
<!-- </mvc:annotation-driven>-->
<!-- <aop:config proxy-target-class="true"></aop:config>-->
<!--</beans>-->
5.在classpath路径下配置ehcache-shiro.xml文件
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
6.密码规则的实现 - CredentialMatcher 比对登录信息和数据库信息是否一致
public class CredentialMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String password = new String(usernamePasswordToken.getPassword());
String dbpassword = (String)info.getCredentials();
return this.equals(password,dbpassword);
}
}
7.自定义过滤器链的实现 有了这一方法之后就不用在配置文件重复添加过滤链接,接配置文件的getMap()
package cn.edu.xidian.B.authurity;
public class FilterChainDefinitionMapBuilder {
@Autowired
private AuthoriseService authoriseService;
private LinkedHashMap<String, String> filterChainDefinitionMap = null;
public LinkedHashMap<String, String> getMap(){
if (filterChainDefinitionMap == null){
//一个线程访问完抛出之后,其它线程才能争取访问
synchronized (this){
if (filterChainDefinitionMap == null){
filterChainDefinitionMap = new LinkedHashMap<>();
setBeforeUrlPermissions();
List<Permission> permissions = authoriseService.getAllPermission();
for (Permission permission : permissions){
if (permission.getStatus() == 0){
//将访问的链接组装起来:perms["pname"] 将之存放到链接的map集合中
filterChainDefinitionMap.put(permission.getPurl(), "perms[\"" + permission.getPname() + "\"");
}
setAfterUrlPermissions();
}
}
}
}
return filterChainDefinitionMap;
}
//在用户登录之前以下这些链接能够在不登录的前提下访问
private void setBeforeUrlPermissions(){
filterChainDefinitionMap.put("/local/*","anon");
filterChainDefinitionMap.put("/403.ftl","anon");
filterChainDefinitionMap.put("/","anon");
}
//这些链接需要在用户登录之后才能访问
private void setAfterUrlPermissions(){
filterChainDefinitionMap.put("/*","authc");
}
}
8.自定义Realm AuthRealm
public class ShiroRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
/**
* shiro认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
// 这里首先通过登录信息获取登录用户名
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String loginName = token.getUsername();
// 判断登录时用户名是否为空或者null
if (loginName != null && !"".equals(loginName)) {
//通过登录名来查询响应的数据库中的用户
User user = userService.getUserByLoginName(loginName);
if (user != null) {
// 如果身份认证验证成功,返回一个AuthenticationInfo实现
return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), getName());
}
}
return null;
}
/**
* shiro授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//PrincipalCollection 是一个身份集合,因为在shiro中同时配置多个realm,所以身份信息有多个,从而提供此集合聚合这些信息
// 使用Shiro提供的方法获取用户名称
String loginName = (String) getAvailablePrincipal(principals);
if (loginName != null) {
String roleId = userService.getRoleIdByLoginName(loginName);
// 获取用户的权限
List<String> permTokens = roleService.getPermTokens(roleId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if (roleId != null) {
// 加入用户角色
info.addRole(roleId);
}
if (permTokens != null) {
// 加入用户许可标记
info.addStringPermissions(permTokens);
}
return info;
}
return null;
}
}
10.Controller实现登录逻辑
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private MenuService menuService;
@Autowired
private UserService userService;
@Autowired
private CompanyService companyService;
@RequestMapping(method = {RequestMethod.GET})
public String login(HttpServletRequest request) {
System.out.println("欢迎登陆!");
return "/login";
}
@RequestMapping(method = {RequestMethod.POST})
public String loginPost(User user, RedirectAttributes redirectAttributes, HttpServletRequest request) {
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getLoginName(), user.getPassword(), user.isRememberMe());
try {
//用户认证
currentUser.login(token);
} catch (AuthenticationException e) {
System.out.println(e);
redirectAttributes.addFlashAttribute("message", "用户名或密码错误!");
return "redirect:/login";
}
if (currentUser.isAuthenticated()) {
//登录成功,保存用户相关信息
sessionHandle(user, request);
//跳转成功页面
return "redirect:/index";
} else {
redirectAttributes.addFlashAttribute("message", "用户名或密码错误!");
return "redirect:/login";
}
}
private void sessionHandle(User user, HttpServletRequest request) {
HttpSession session = request.getSession();
User loginUser = userService.getUserByLoginName(user.getLoginName());
if(loginUser != null){
session.setAttribute("companyId", loginUser.getCompanyId());
session.setAttribute("username", loginUser.getNickName());
session.setAttribute("userId", loginUser.getId());
}
//menuService.updateMenuInSession(request);
}
}
11.本地LocalController实现
package cn.edu.xidian.B.controller;
@Controller
@RequestMapping("/local")
public class LocalController {
private static Logger LOG = Logger.getLogger(LocalController.class);
@Autowired
private StudentService studentService;
@Autowired
private TeacherService teacherService;
@RequestMapping("/")
public String login() {
return "local/local";
}
@RequestMapping("/dologin")
public String dologin(HttpServletRequest request, HttpServletResponse response, Model model)
throws Exception {
HttpSession session = request.getSession();
String uid = request.getParameter("uid");
System.out.println(uid);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(uid,"");
try {
subject.login(token);
} catch (AuthenticationException e) {
LOG.error(e.getMessage());
// response.sendRedirect("/403");
model.addAttribute("message", "登录失败,系统没有您的信息");
return "local/err";
}
if (uid == null) {
model.addAttribute("message", "uid 为空");
return "local/err";
} else {
session.setAttribute("uid", uid);
if (studentService.findStudentBySno(uid) != null) {
response.sendRedirect("/student/main");
} else if (teacherService.findByStaffno(uid) != null) {
if(teacherService.findByStaffno(uid).getType() == 2){
response.sendRedirect("/teacher/main");
} else {
response.sendRedirect("/testteacher/main");
}
} else {
model.addAttribute("message", "不在表中 uid = " + uid);
return "local/err";
}
}
return null;
}
}
12.前台实现 在这里省略
总结:在这一系列实现之后,我们就能通过Shiro拦截认证来拦截用户的登录情况。对,还有数据库的实现,特别需要注意的是权限与用户对应,然后用户的类型与权限对应等。实践之后就会觉得,其实并没有那么简单,还是需要考虑很多情况的。
下面是在前期学习的时候,做的一些记录
通过注解配置授权
这里授权有两种方式,编程方式和注解方式。编程方式,subject.hasRole();
1.在pom.xml中加入相关依赖
//加入aspectjweaver包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.在springmvc.xml中增加配置
<!--开启Shiro注解-->
<aop:config proxy-target-class="true"/>
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<!--设置Shiro的securityManager-->
<property name="securityManager" ref="securityManager"/>
</bean>
3.使用
在Controller中使用注解@RequiresPermissions("admin:delete");测试只有admin拥有delete权限才能访问。
在Controller中使用注解@RequiresRoles("admin");测试只有是admin角色才能访问。
Shiro过滤器
1.Shiro内置过滤器
1.认证
anon
authBasic
authc
user
logout
2.授权
perms
roles
ssl
port
2.自定义ShiroFilter
filterChainDefinitions - 授权规则定义
Shiro通过ShiroFilterFactoryBean类的filterChainDefinitions(授权规则定义)属性转换为一个filterChainDefinitionMap,转换完成后交给ShiroFilterFactoryBean保管。
//拦截数据库url
public class FilterChainDefinitionMapBuilder {
@Autowired
private SysAuthoriseService sysAuthoriseService;
private LinkedHashMap<String,String> filterChainDefinitionMap = null;
public LinkedHashMap<String,String> getMap() {
if(filterChainDefinitionMap == null) {
synchronized (this) {
if(filterChainDefinitionMap == null) {
filterChainDefinitionMap = new LinkedHashMap<>();
setBeforeUrlPermissions();
List<SysPermission> sysPermissions = sysAuthoriseService.getAllSysPermission();
for(SysPermission p : sysPermissions) {
if(p.getStatus() == 0)
filterChainDefinitionMap.put(p.getPurl(), "perms[\"" + p.getPname() + "\"]");
}
setAfterUrlPermissions();
}
}
}
return filterChainDefinitionMap;
}
private void setBeforeUrlPermissions() {
filterChainDefinitionMap.put("/local/*", "anon");
filterChainDefinitionMap.put("/403.html", "anon");
filterChainDefinitionMap.put("/","anon");
filterChainDefinitionMap.put("/student/logout","anon");
filterChainDefinitionMap.put("/teacher/logout","anon");
}
private void setAfterUrlPermissions() {
//需要用户登录才能访问
filterChainDefinitionMap.put("/*", "authc");
}
}
注意:此处使用的拦截器还需要在spring.xml文件中配置。
<bean class="cn.edu.xidian.see.authurity.FilterChainDefinitionMapBuilder" id = "filterChainDefinitionMapBuilder"></bean>
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getMap"></bean>
Shiro会话(Session)管理
1.SessionManager
//1.首先在pom.xml中加入Redis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
//2.然后重写SessionDao
public class RedisSessionDao extends AbstractSessionDAO{
//在重写这个SessionDao之后由增删改查等方法
}