1.shiro基础
-
简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
-
主要功能
- 三个核心组件:Subject, SecurityManager 和 Realms.
- Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
- Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
- Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
- Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
2.与SSM整合
-
首先创建好ssm的maven项目,创好目录
-
配置文件(除了ssm原本的配置还需要配置的配置文件)
- 添加pom依赖
<properties> <shiro.version>1.3.2</shiro.version> </properties> <!-- shiro start --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.8</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <!-- end shiro -->
- web.xml添加配置
<!-- Shiro Filter is defined in the spring application context: --> <filter> <filter-name>shiroFilter</filter-name> <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>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 添加spring-shiro.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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- ========================================================= Shiro Core Components - Not Spring Specific ========================================================= --> <!-- Shiro's main business-tier object for web-enabled applications (use DefaultSecurityManager instead when there is no web environment)--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="realm" ref="jdbcRealm"/> </bean> <!-- Let's use some enterprise caching support for better performance. You can replace this with any enterprise caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <!-- Used by the SecurityManager to access security data (users, roles, etc). Many other realm implementations can be used too (PropertiesRealm, LdapRealm, etc. --> <bean id="jdbcRealm" class="com.wangle.shiro.web.shiro.ShrioRealm"> <!--加密算法配置--> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--加密算法名称--> <property name="hashAlgorithmName" value="MD5"/> <!--加密的次数--> <property name="hashIterations" value="3"></property> <!--<property name="storedCredentialsHexEncoded" value="false"/>--> </bean> </property> </bean> <!-- ========================================================= Shiro Spring-specific integration ========================================================= --> <!-- Post processor that automatically invokes init() and destroy() methods for Spring-configured Shiro objects so you don't have to 1) specify an init-method and destroy-method attributes for every bean definition and 2) even know which Shiro objects require these methods to be called. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run: --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml - web.xml uses the DelegatingFilterProxy to access this bean. This allows us to wire things with more control as well utilize nice Spring things such as PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!--未认证访问页面,未登录时访问任意非anno(匿名拦截器)拦截页面,会自动跳转到的页面--> <property name="loginUrl" value="/login.html"/> <!--认证成功访问页面--> <property name="successUrl" value="/index.html"/> <!--无权限页面,当访问自己不具备权限的页面会自动跳转到的页面--> <property name="unauthorizedUrl" value="/unauthorized.html"/> <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean defined will be automatically acquired and available via its beanName in chain definitions, but you can perform overrides or parent/child consolidated configuration here if you like: --> <!-- <property name="filters"> <util:map> <entry key="aName" value-ref="someFilterPojo"/> </util:map> </property> --> <!--通过数据库配置资源管理--> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/> <!--通过配置文件配置资源管理--> <!-- <property name="filterChainDefinitions"> <value> <!– 请求的url资源=拦截器 anon 匿名拦截器(未认证的情况下允许访问) authc 认证拦截器(认证成功的情况下允许访问) logout 退出拦截器(认证成功后进行清除认证操作) roles[角色名称,角色名称...] (,表示需要多个权限,and关系)权限过滤器(只有具备指定权限的人才能访问对应权限的页面) –> /user/logout=logout /user/login=anon /student.html=roles[stu] /admin.html=roles[admin] /**/*.png=anon /**/*.jpg=anon /**/*.css=anon /**/*.js=anon /** = authc </value> </property>--> </bean> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionsBuilder" factory-method="builder"/> </beans>
- 添加ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" dynamicConfig="false"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="1000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <cache name="cache_test" maxElementsInMemory="2" eternal="false" timeToIdleSeconds="60" timeToLiveSeconds="60" maxElementsOnDisk="2" overflowToDisk="true" diskPersistent="false" memoryStoreEvictionPolicy="FIFO" /> </ehcache>
-
编写Controller代码,进行身份验证
@RequestMapping(value="login",method = RequestMethod.GET) public Object login(@RequestParam String username, String password) { Map<String, Object> map = new HashMap<>(); map.put("code",0); /*shiro进行权限管理操作*/ //获取正在请求认证的用户 Subject currentUser = SecurityUtils.getSubject(); //为了方便测试,加的第二次登陆之前先清除第一次登陆的状态,后期要删除 if (currentUser.isAuthenticated()) { System.out.println("正在清除上一个用户的登陆状态!!"); currentUser.logout(); } if (!currentUser.isAuthenticated()) { System.out.println(username+" : "+password); UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) {//用户名不存在 map.put("code",1); map.put("msg",uae.getMessage()); } catch (IncorrectCredentialsException ice) {//凭证匹配异常 map.put("code",1); map.put("msg","密码输入有误!"); } catch (LockedAccountException lae) {//锁定用户异常 map.put("code",1); map.put("msg",lae.getMessage()); } catch (AuthenticationException ae) {//认证异常 map.put("code",1); map.put("msg",ae.getMessage()); } } return map; }
-
编写xxRealm.class,进行认真和授权(下面仅含有认真,授权往后)
package com.wangle.shiro.web.shiro; import com.wangle.shiro.bean.Student; import com.wangle.shiro.service.StudentService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.Set; /** * @ClassName ShrioRealm * @description TODO * * @author wangle * @date 2019/12/17 * @version 1.0 */ public class ShrioRealm extends AuthorizingRealm { @Autowired StudentService studentService; /** * 进行角色授权 * 1.当访问需要权限的页面时会执行 * 2.第一次访问得到权限之后,以后就不会再访问该方法 * @param principalCollection 主题信息(一般为用户名) * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { Set<String> roles; String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal(); System.out.println("正在进行获取权限的用户是"+primaryPrincipal); roles = studentService.selectRNameByUsername(primaryPrincipal); System.out.println("获得权限是"+roles); if (roles.contains("admin")) { roles = studentService.selectAllRName(); } AuthorizationInfo info = new SimpleAuthorizationInfo(roles); return info; } /** * 进行角色认证 * 当调用currentUser.login(token)时会调用 * @param token UsernamePasswordToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; Student student = studentService.selectByUsername(upToken.getUsername()); if (student == null) { throw new UnknownAccountException("用户名不存在"); } if (student.getStatus() == 1) { throw new LockedAccountException("该用户因违规已被锁定"); } /* *参数类型 *principal(主要的): 传入一个唯一的具有标识性的列,一般为用户名 *credentials(凭证): 传入凭证,一般为密码 * ByteSource: 使用盐值加密的源 * RealmName: super.getName(); */ Object principal = student.getUsername(); Object credentials = student.getPassword(); // AuthenticationInfo info = new SimpleAuthenticationInfo(principal,credentials,super.getName()); /* * 使用盐值加密的进行比较 */ ByteSource credentialsSalt = ByteSource.Util.bytes(principal); AuthenticationInfo info = new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,super.getName()); return info; } public static void main(String[] args) { String hashAlgorithmName = "MD5"; Object salt = ByteSource.Util.bytes("lisi"); String credentials = "123456"; Object newCredentials = new SimpleHash(hashAlgorithmName, credentials, salt, 3); System.out.println(newCredentials); } }
-
注册时需要的加密代码
public static void main(String[] args) { //加密的名称,可以查看shiro底层提供的名称 String hashAlgorithmName = "MD5"; //加密时使用的盐值 Object salt = ByteSource.Util.bytes("lisi"); //要加密的密码 String credentials = "123456"; //调用shiro底层的加密算法 Object newCredentials = new SimpleHash(hashAlgorithmName, credentials, salt, 3);//3是加密次数,要保证和配置文件一致 System.out.println(newCredentials); }
-
使用数据库存储页面权限需要的配置类
package com.wangle.shiro.web.shiro; import com.wangle.shiro.bean.Resource; import com.wangle.shiro.service.ResourceService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @author wangle * @version 1.0 * @ClassName FilterChainDefinitionsBuilder * @description TODO * @date 2019/12/31 */ @Component public class FilterChainDefinitionsBuilder { @Autowired ResourceService resourceService; public Map builder() { Map<String, String> map = new LinkedHashMap<>(); List<Resource> resources = resourceService.selectAll(); for (Resource resource : resources) { if (resource.getStatus() != 0) { map.put(resource.getName(),resource.getValue()); } } System.out.println("页面资源文件为"+map); return map; } }
-
数据库表(存储资源路径及对应的访问权限)
1.详细学习
-
拦截器
<value> <!-- 请求的url资源=拦截器 anon: 匿名拦截器(未认证的情况下允许访问) authc: 认证拦截器(认证成功的情况下允许访问) logout:退出拦截器(认证成功后进行清除认证操作) roles: [角色名称,角色名称...] (,表示需要多个权限,and关系)权限过滤器(只有具备指定权限的人才能访问对应权限的页面) --> /user/logout=logout /user/login=anon /**/*.png=anon /**/*.jpg=anon /**/*.css=anon /**/*.js=anon /** = authc </value>
-
shiro标签
<shiro:guest> 游客访问 <a href = "login.jsp"></a> </shiro:guest> user 标签:用户已经通过认证\记住我 登录后显示响应的内容 <shiro:user> 欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a> </shiro:user> authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的 <shiro:authenticted> 用户[<shiro:principal/>] 已身份验证通过 </shiro:authenticted> notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证 <shiro:notAuthenticated> 未身份验证(包括"记住我") </shiro:notAuthenticated> principal 标签:显示用户身份信息,默认调用 Subjec.getPrincipal()获取,即Primary Principal <shiro:principal property = "username"/> hasRole标签:如果当前Subject有角色将显示body体内的内容 <shiro:hashRole name = "admin"> 用户[<shiro:principal/>]拥有角色admin </shiro:hashRole> hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容 <shiro:hasAnyRoles name = "admin,user"> 用户[<shiro:pricipal/>]拥有角色admin 或者 user </shiro:hasAnyRoles> lacksRole:如果当前 Subjec没有角色将显示body体内的内容 <shiro:lacksRole name = "admin"> 用户[<shiro:pricipal/>]没有角色admin </shiro:lacksRole> hashPermission:如果当前Subject有权限将显示body体内容 <shiro:hashPermission name = "user:create"> 用户[<shiro:pricipal/>] 拥有权限user:create </shiro:hashPermission> lacksPermission:如果当前Subject没有权限将显示body体内容 <shiro:lacksPermission name = "org:create"> 用户[<shiro:pricipal/>] 没有权限org:create </shiro:lacksPermission>
-
注解
@RequiresAuthenthentication:表示当前Subject已经通过login进行身份验证;即 Subjec.isAuthenticated()返回 true @RequiresUser:表示当前Subject已经身份验证或者通过记住我登录的, @RequiresGuest:表示当前Subject没有身份验证或者通过记住我登录过,即是游客身份 @RequiresRoles(value = {"admin","user"},logical = Logical.AND):表示当前Subject需要角色admin和user @RequiresPermissions(value = {"user:delete","user:b"},logical = Logical.OR):表示当前Subject需要权限user:delete或者user:b
-
开启shiro注解配置mvc配置文件添加(未测试)
<!-- 开启shiro的注解支持 --> <bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <!-- 必须改为true,即使用cglib方式为Action创建代理对象。默认值为false,使用JDK创建代理对象,会造成问题 --> <property name="proxyTargetClass" value="true"></property> </bean> <!-- 使用shiro框架提供的切面类,用于创建代理对象 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>
来源:CSDN
作者:Fulleea
链接:https://blog.csdn.net/qq_40198779/article/details/103875203