Shiro
Shiro
- shiro是什么
- 是要给权限框架(安全框架)
- 类似的框架Spring Security
- shiro中的核心组件
- subject:和Shiro交互的主体都是subject
- SecurityManager:shiro核心的组件
- realm:安全数据源
- shiro能干什么
- 认证
- 授权
- 密码加密
- 会话管理
- 缓存
1.环境搭建
1.1导入jar包
shiro-all-1.3.2
shiro-core-1.3.2
1.2新建配置文件shiro.ini
[users]
#用户名=密码,角色1,角色2
admin=123,admin
qfadmin=123,qfadmin
[roles]
#角色名称=权限
admin = *
qfadmin=user:add,user.update
1.3测试
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// 1.创建SecurityManagerFactory
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.通过SecurityManagerFactory得到securityManager
SecurityManager securityManager = factory.getInstance();
// 3.把securityManager设置到securityUtils,方便后期的获取(全局)
SecurityUtils.setSecurityManager(securityManager);
// 4.创建Subject
Subject currentUser = SecurityUtils.getSubject();
// 5.给Subje创建一个sesion
Session session = currentUser.getSession();
// 6.给属性添加一个键值对
session.setAttribute( "someKey","aValue" );
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 7.根据key获取value
String value = (String) session.getAttribute( "someKey" );
// 8.判断
if (value.equals("aValue")) {
log.info("从session中获取的value [" + value + "]");
}
// 9.判断用户是否登录
if (!currentUser.isAuthenticated()){ // //如果没有登录就进行登录操作
//封装用户名或密码
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
//token.setRememberMe ( true); //设置记住我
try {
//登录
currentUser.login(token);
} catch (UnknownAccountException uae){ //未知的账户异常:用户不存在
log.info("There is no user with username of " +token.getPrincipal());
} catch (IncorrectcredentialsException ice) { //用户名或密码错误
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { //账户被锁定
log.info("The account for username " + token.getPrincipal() + " is locked. "
+"Please contact your administrator to unlock it.");
} catch (AuthenticationException ae) { //其他认证异常,
}
}
// 10.获取登录的用户名
log.info("欢迎["+ currentUser.getPrincipal() + "]登录成功。。");
// 11.判断该用户是否有某个角色
if (currentUser.hasRole("schwartz")) {
log.info("该用户有【schwartz】角色");
}else {
log.info("该用户没有【schwartz】角色");
}
// 12.判断用户是否有这个权限
if (currentUser.isPermitted("lightsaber : weild")) {
log.info("该用户有【lightsaber :weild】权限");
} else {
log.info("该用户没有【lightsaber : weild】权限");
}
// 13.注销
currentUser.logout();
// 14.
System.exit(0);
}
2.shiro和spring整合
Shiro和Spring整合
- 添加jar包的依赖
- spring
- shiro
- 配置文件
- web.xml
- 初始化Spring容器的监听器
- shiroFilter
- /*
- springmvc.xml
- 开启包扫描
- 。。。。
- applicationContext.xml
- securityManager
- lifecycleBeanPostProcessor
- shiroFilter
- 登陆成功后跳转的路径
- 没有登陆时跳转登陆页面路径
- 没有权限时跳转的路径
- 一些过滤器
- web.xml
2.1导入依赖
<properties>
<shiro.version>1.3.2</shiro.version>
</properties>
<!--shiro的依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-guice</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-hazelcast</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
2.2在applicationContext.xml中配置
<!-- shiro的核心组件-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionMode" value="native"/>
<!--把Realm配置到securityManager-->
<property name="realm" ref="userRealm"/>
<!--加入会话管理器-->
<property name="sessionManager" ref="sessionManager"/>
<!--加入rememberMeManager管理-->
<property name="rememberMeManager" ref="rememberMeManager"/>
<!--加入cacheManager管理-->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- shiro的Realm ,自定义UserRealm-->
<bean id="userRealm" class="com.dtf.realm.UserRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密的算法-->
<property name-"hashAlgorithmName" valuem"MD5"></property>
<!--加密次数-->
<property name="hashIterations" walue="1024"></property>
</bean>
</property>
<!--启用缓存,默认false: -->
<property name="cachingEnabLed" value="true"/>
<!--启用身份验证缓存,即缓存AuthenticationInfo信息,默认false -->
<property name="authenticationcachingEnabled" value="true"/>
<!--缓存AuthenticationInfo信息的缓存名称-->
<property name="authenticationcacheName" value="authenticationcache"/>
<!--启用授权缓存,即缓存AuthorizationInfo信息,默认false -->
<property name="authorizationcachingEnabled" value="true"/>
<!--缓存AuthorizationInfo信息的缓存名称-->
<property name="authorizationcacheName" value="authorizationcache"/>
</bean>
<!-- 可以自动配置spring容器中shiro bean 的生命周期方法-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.lifecycleBeanPostProcessor"/>
<!-- shiroFilter:很重要-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--引入securityManager-->
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" ref="/ok.jsp"/>
<property name="unauthorizedUrl" ref="/unauthorized.jsp"/>
<!--过滤器
authc:必须要认证通过后才可以调用,通过记住我登陆不算
anon:匿名的过滤器
logout:注销
user:认证成功或者通过记住我登陆成功都可以访问
-->
<property name="filterChainDefinitions">
<value>
/logout = logout
/favicon.ico = anon
/logo.png = anon
/shiro.css = anon
/s/login = anon
/*.jar = anon
/add = roles[admin]
/update = perms[user:update]
/** = user
/** = authc
</value>
</property>
</bean>
2.2.1 URL匹配模式
?:匹配一个字符
*:匹配零个或多个字符串
**:匹配路径中的零个或多个路径
2.3创建realm并在xml中配置
public class UserRealm extends AuthorizingRealm{
//重写AuthorizationInfo(授权)和AuthenticationInfo(身份认证)
/**
*授权
*/
Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
//TOD0 Auto-generated method stub
System.out.println("UserRealm.dcGetAuthorizationInfo()");
SimpleAuthorizationInfo saiz = new SimpleAuthorizationInfo();
//获取当前用户名称
0bject username = principals.getPrimaryPrincipal();
if("admin".equals(username.toString())){
System.out.println("给admin添加role角色");
HashSet<String> roles = new HashSet<String>();
roles.add("admin");
saiz.addRoles(roles);//添加角色
} else if("qfAdmin".equals(username.toString())){
Set<String> permissions - new HashSet<String>();
permissions.add("user:udpate");
saiz.addStringPermissions(permissions);//添加权则
}
return saiz;
}
/**
*身份验证
*/
eoverride
protected AuthenticationInfo docetAuthenticationInfo( AuthenticationToken token) throws AuthenticationExce{
//1.根据用户名查询密码
Object username = token.getPrincipal();//获取的是UsernamePasswordToken对象中的第一个参数
String password = "admin";
if("qfadmin".equals(username)){
password="qfadmin";
}
//2.封装用户的密码
//第一个参数是:用户名
//第二个参数是:从数据库中查询出的密码
//第三个参数是:realm名字
SimpleAuthenticationInfo asci = new SimpleAuthenticationInfo(username,password,getName());
// ByteSource salt = ByteSource.Util.bytes(username);
// SimpleAuthenticationInfo asci = new SimpleAuthenticationInfo(username,password,salt,getName());
System.out.println("userRealm.doGetAuthenticationInfo()");
//3.返回给shiro
return asci;
}
}
示例:
<!-- shiro的Realm ,自定义UserRealm-->
<bean id="userRealm" class="com.dtf.realm.UserRealm"></bean>
<!-- 在shiro的核心组件中添加-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" value="userRealm"/>
</bean>
2.4在web.xml中配置shiro过滤器
<!-- 这个filter的名字要和spring容器中的shiroFilter的id要保持一致,xml中-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 拦截到所有的请求,交给spring容器中的shiroFilter处理-->
<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>
2.5测试
package com.dtf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Usercontroller {
@RequestMapping(value = "/login")
public string login(String username,String password,String rememberMe) {
//1.先得到当前用户
Subject currentUser = SecurityUtiles.getSubject();
//1.判断是否登录
if (!currentUser.isAuthenticated()){ // //如果没有登录就进行登录操作
//封装用户名或密码
UsernamePasswordToken token m new UsernamePasswordToken(username,password);
try {
if("1".equals(rememberMe)){
token.setRememberMe (true); //设置记住我
}
//登录
currentUser.login(token);
} catch (UnknownAccountException uae){ //未知的账户异常:用户不存在
System.out.println("There is no user with username of " +token.getPrincipal());
} catch (IncorrectcredentialsException ice) { //用户名或密码错误
System.out.println("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { //账户被锁定
System.out.println("The account for username " + token.getPrincipal() + " is locked. "
+"Please contact your administrator to unlock it.");
} catch (AuthenticationException ae) { //其他认证异常,
System.out.println("认证失败");
return "login";
}
}
return "ok";
}
@RequiresPermissions(value = "user:add")//必须有该权限才可以访问
@RequestMapping(value = "/add")
public String add(){
System.out.println("UserController.add");
return "ok";
}
@RequiresRoles(value = "admin")//必须有该角色才可以访问
@RequestMapping(value = "/update")
public String update(){
System.out.println("UserController.update");
return "ok";
}
}
package com.dtf.relam;
public class MD5Utiles{
public static void main(String[] args){
// 1.加密算法
String algorithmName = "MD5";
// 2.名码
0bject source = "admin";
// 3.盐值
0bject salt = ByteSource.Util.bytes("admin");//盐值一般是用户名或者userId
// 4,加密次数
int hashIterations = 1024;
SimpleHash simpleHash = new SimpleHash(algorithmName,source,salt,hashIterations);
// 5.得到加密后的密码
System.out.println(simpleHash);
}
}
3.身份认证(见2.5)
登录认证
- realm
- 认证
- 授权
- 权限
- 角色
- 很多的过滤器
4.密码加密(见2.5)
加密
- 盐值加密解决加密后密码一致的问题
5.授权
控制权限
- 过滤器:没有权限会直接跳转到没有权限页面
- 注解和标签:没有权限会直接抛出异常
5.1 授权方式
1.编程式
1.注解式
@RequiresRoles(value = "admin")//必须有该角色才可以访问
@RequestMapping(value = "/add")
public String add(){
System.out.println("UserController.add");
return "ok";
}
@RequiresPermissions(value = "user:update")//必须有该权限才可以访问
@RequestMapping(value = "/update")
public String update(){
System.out.println("UserController.update");
return "ok";
}
在springmvc.xml中添加:
<aop:config proxy-target-class="true" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSource">
<property name="securityManager" ref="securityManager" />
</bean>
常用注解
1.@RequiresRoles
//:表示当前Subject需要角色admin和user
@RequiresRoles(value={"admin","user"}, logical= Logical.AND)
2.@RequiresPermissions
//:表示当前Subject需要权限user:a 或user:b
@RequiresPermissions (value={"user:a","user:b"}, logical=Logical.OR)
给登陆用户授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1.得到登录用户
String username = (String)principals.getPrimaryPrincipal();
Set<String> permissionSet = new HashSet<String>();
if("admin".equals(username)){
permissionSet.add("user:add");
permissionSet.add( "user:query"); // admin用户具有user:add和user: query权限
}else if("qf".equals(username)){
permissionSet.add( "user:update" );
permissionSet.add( "user:delete" ); // qf用户具有user:update和user:delete权限
}
//接收权限相关的信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setstringPermissions(permissionSet); //收置权限
// simpleAuthorizationInfo.setRoles(roles);//这里也可以设置角色
return simpleAuthorizationInfo;
}
2.JSP/GSP 标签
在JSP/GSP页面通过相应的标签完成
<shiro:hasRole name="admin">
<!--有权限-->
</shiro:hasRole>
shiro标签
1.导入标签库
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro”%>
2.guest标签
用户没有身份验证时显示相应信息,即游客访问信息:
<shiro:guest>
欢迎游客访问,<a href="${pageContext.request.contextPath}/login.jsp">登录</a>
</shiro:guest>
3.user标签
用户己经经过认证/记住我登录后显示相应的信息。
<shiro:user>
欢迎[<shiro:principal/>]登录,<a href="${pageContext.request.contextPath}/logout">退出</a></shiro:user>
4.authenticated标签
用户已经身份验证通过,即 Subject.login登录成功,不是记住我登录的。
<shiro:authenticated>
用户[<shiro:principal/>]已身份验证通过
</shiro:authenticated>
4.notAuthenticated标签
用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
<shiro:notAuthenticated>
未身份验证(包括记住我)
</shiro:notAuthenticated>
5.principal标签
显示用户身份信息,默认调用Subject.getPrincipal()获取,即 Primary Principal。
<shiro: principal/>
6.hasRole标签
如果当前Subject有角色将显示body体内容。
<shiro:hasRole name="admin">
用户[<shiro:principal/>]拥有角色 admin<br/>
</shiro:hasRole>
7.hasAnyRoles标签
<shiro:hasAnyRoles name="admin,user">
用户[<shiro:principal/>]拥有角色 admin 或user<br/>
</shiro:hasAnyRoles>
8.lacksRole标签
如果当前Subject没有角色将显示 body体内容。
<shiro:lacksRole name="abc">
用户[<shiro:principal/>]没有角色 abc<br/>
</shiro:lacksRole>
6.RememberMe
applicationContext.xml中配置:
<!-- 会话Cookie模板-->
<bean id="rememberNeCookie" class="org.apache.shiro.web.servlet.SimpleCoohie">
<constructor-arg value="sid" />
<!--设置js是否可以访问cookie,true不能访问-->
<property name="httpOnly" value="true"/>
<!--保存时长30天,以秒为单位-->
<property name="maxAge" value="2592000" />
</bean>
<!-- rememberMe管理器-->
<bean id="rememberMeManager" class="org.apache, shiro.web.mgt.CookieRememberNeManage">
<!-- ipherKey是加密remembenMe Cookie的密钥;默认AES算法-->
<property name="cipherKey" value="#{T(org.apache,shiro.codec.Base64).decode('4AvVhmFLUsOKTA3Kprsdag==')}"/>
<!--引入上面定义的cookie模板-->
<property name="cookie" ref="rememberMeCookie" />
</bean>
<!-- shiro的核心组件-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- shiroFilter:很重要-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--引入securityManager-->
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" ref="/ok.jsp"/>
<property name="unauthorizedUrl" ref="/unauthorized.jsp"/>
<!--过滤器
user:认证成功或者通过记住我登陆成功都可以访问
-->
<property name="filterChainDefinitions">
<value>
/** = user
</value>
</property>
</bean>
为什么要换成user拦截器呢,下面就要说说user和authc的区别了,在上面的比较中也有。
user
用户己经身份验证或者记住我登录的都可以访问。
authc
必须认证之后才可以访问,通过记住我登录不能进行访问。用authc来检查一些关键操作,比如支付,即使用户使用了记住我登录也要再次确认用户的身份,以确保当前用户还是你。
@Controller
public class Usercontroller {
@RequestMapping(value = "/login")
public string login(String username,String password,String rememberMe) {
UsernamePasswordToken token m new UsernamePasswordToken(username,password);
if("1".equals(rememberMe)){
token.setRememberMe (true); //设置记住我
}
}
}
7.shiro缓存
shiro的缓存
- 配置缓存管理器
- 缓存管理器添加到核心组件中
- 在realm中设置启动缓存
- 开启shiro缓存
- 开启省份认证的缓存
- 设置是否认证使用的缓存策略
- 开启授权的缓存
- 设置授权缓存使用的策略
- 在ehcache.xml中添加省份认证和授权的缓存策略
Shiro提供了类似于Spring 的Cache抽象,即 shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现。这里我们使用Ehcache。
7.1Realm缓存
为什么要使用Realm 缓存?
现在我们学习了认证和授权后会发现这么一个问题,用户每发一次请求shiro都会调用授权的方法。而真正的项目中用户的权限是动态的从数据库中查询的,这样的就导致用户每发一次请求都要查询该用户的权限,这样的肯定会存在性能问题。而一般我们查询用户的权限只需要查询一次就可以,这时候就可以利用缓存。第一次查询出来后放入到缓存里面,下次直接去缓存中获取,当用户更新权限后也要清空缓存。
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要原因是授权信息缓存,因为授权的数据量大。
用户认证通过。
该用户第一次授权:调用realm查询数据库
该用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
1.导入EhCache的jar包和EhCache的配置文件
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
ehcache.xml从ehcache的jar包中解压到项目
<ehcache xmlns:xsim"http://www.w3.org/2801/XNLSchema-instance" xsi:noNamespaceSchemaL
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToldleSecondsm"120"
timeToLiveSeconds="120"
maxElementsonDisk="10088080"
diskExpiryThreadIntervalSeconds="120"
memorystoreEvictionPolicy="LRU">
<persistence strategy="LocalTempSwap"/>
</defaultcache>
<cache name="authorizationCache"
maxEntriesLocalHeap="2003"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationcache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSecondsm"3600"
timeToLiveSeconds="3"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
2.配置EhCache缓存
2.1在spring中开启缓存(applicationContext.xml)
<!--缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- cache配置文件-->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- shiro的核心组件-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--加入cacheManager管理-->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- shiro的Realm ,自定义UserRealm-->
<bean id="userRealm" class="com.dtf.realm.UserRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密的算法-->
<property name-"hashAlgorithmName" valuem"MD5"></property>
<!--加密次数-->
<property name="hashIterations" walue="1024"></property>
</bean>
</property>
<!--启用缓存,默认false: -->
<property name="cachingEnabled" value="true"/>
<!--启用身份验证缓存,即缓存AuthenticationInfo信息,默认false -->
<property name="authenticationcachingEnabled" value="true"/>
<!--缓存AuthenticationInfo信息的缓存名称-->
<property name="authenticationcacheName" value="authenticationcache"/>
<!--启用授权缓存,即缓存AuthorizationInfo信息,默认false -->
<property name="authorizationcachingEnabled" value="true"/>
<!--缓存AuthorizationInfo信息的缓存名称-->
<property name="authorizationcacheName" value="authorizationcache"/>
</bean>
springmvc全局的异常处理
<!--全局的异常处理-->
<bean
class="org.springframework.web.servlet.handler.SimpLeMappingExceptionResolver">
<!--默认的错误视图页面-->
<property name="defaultErrorview" valuem"error" />
<!--错误视图页面可以通过${ex}获取异常信息 -->
<property name="exceptionAttribute" value="ex" />
<property namem"exceptionMappings">
<props>
<!--
异常处理和制.
系统出现异常后先看当前异常是否配置
a)如果配置了当前异常就跳转到对应的错误视图
b)如果没有配置
1)再看当前异常的父类是否配置
a)如果配置了就通知到对应的错误视图
b)如果父类异常异常也没有配置,就跳转到默认的错误视图
-->
<prop key="java.lang.RuntimeException">
error1
</prop>
</props>
</property>
</bean>
maven项目启动,添加tomcat插件
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuraction>
<contextReloadable>true</contextReloadable>
<port>8080</port>
<path>/</path>
</configuraction>
</plugin>
</plugins>
</build>