来源于周立,经过自己的整理学习
1. Shiro 能做什么
① 认证:验证用户来核实他们的身份
② 授权:对用户执行访问控制,如:
判断用户是否被分配了一个确定的安全角色
判断用户是否被允许做某事
③ 会话管理:在任何环境下使用 Session API,即使没有 Web 或 EJB 容器。
④ 加密:以更简洁易用的方式使用加密的功能,保护或隐藏数据防止被偷窥
⑤ Realms:聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”。
⑥ 启用单点登录(SSO)功能。
⑦ 为没有关联到登录的用户启用"Remember Me"服务
2.简单的代码实现helloword
使用shiro默认的配置实现登陆方式,简单实现了认证和授权
config.ini
[users]
user1 = cc,role1
[roles]
role1 = p1,p2
helloword
public class HelloWorld {
public static void main(String[] args) {
// 读取ini文件,得到一个工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:config.ini");
// 获得securityManager
SecurityManager manager = factory.getInstance();
SecurityUtils.setSecurityManager(manager);
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cc");
token.setRememberMe(true);
// 这里获得的其实是一个代理
Subject currentUser = SecurityUtils.getSubject();
// 登陆成功就过,登陆不成功就抛异常
currentUser.login(token);
boolean permitted = currentUser.isPermitted("p3");
System.out.println(permitted);
}
}
pom文件依赖的jar(后面代码用到的jar)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- shiro begin -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</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>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro end -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
3、Shiro 的自定义身份认证-的 Realm 自定义和多个 Realms 的处理
MyRealm1
public class MyRealm1 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 通过用户名去获得用户的所有资源,并把资源存入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> s = new HashSet<String>();
s.add("p1");
s.add("p2");
info.setStringPermissions(s);
Set<String> r = new HashSet<String>();
r.add("r1");
r.add("r2");
info.setRoles(r);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {
// token中储存着输入的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = String.valueOf(upToken.getPassword());
// 通常是与数据库中用户名和密码进行比对,这里就省略了
// 比对成功则返回info,比对失败则抛出对应信息的异常AuthenticationException
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,
password.toCharArray(), this.getName());
return info;
}
}
MyRealm2
public class MyRealm2 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// 通过用户名去获得用户的所有资源,并把资源存入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> s = new HashSet<String>();
s.add("p1");
s.add("p2");
info.setStringPermissions(s);
Set<String> r = new HashSet<String>();
r.add("r1");
r.add("r2");
info.setRoles(r);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken)throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
token.getUsername(), token.getPassword(), this.getName());
// 故意抛出一个异常,让MyRealm2认证不通过.
if (true) {
throw new AuthenticationException("MyRealm2 认证失败");
}
return info;
}
}
Helloword
public class HelloWorld {
public static void main(String[] args) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(
"classpath:config.ini");
SecurityManager manager = factory.createInstance();
SecurityUtils.setSecurityManager(manager);
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cc");
token.setRememberMe(true);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
System.out.println(currentUser.isPermitted("p1"));
}
}
配置文件 config.ini
[main]
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
#配置认证策略,此处配置的是所有realm都必须认证通过方才成功
authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
authenticator.authenticationStrategy = $authcStrategy
myRealm1 = com.itmuch.shiro.MyRealm1
myRealm2 = com.itmuch.shiro.MyRealm2
authenticator.realms = $myRealm2,$myRealm1
#最后将authenticator注入给securityManager
securityManager.authenticator = $authenticator
关于 AuthenticationStrategy解释:
① AtLeastOneSuccessfulStrategy :如果一个(或更多)Realm 验证成功,则整体的尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。
② FirstSuccessfulStrategy 只有第一个成功地验证的 Realm 返回的信息将被使用。所有进一步的 Realm 将被忽略。如果没有一个验证成功,则整体尝试
失败
③ AllSucessfulStrategy 为了整体的尝试成功,所有配置的 Realm 必须验证成功。如果没有一个验证成功,则整体尝试失败。
④ ModularRealmAuthenticator 默认的是 AtLeastOneSuccessfulStrategy
4、自定义认证策略
MyRealm1
MyRealm2
同上
自定义的认证策略 MyAuthenticationStrategy.java
/**
* 定义自己的认证策略,本例中,达到的目的是myRealm2认证不通过则认为认证不过
* @author ITMuch
*/
public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy {
@Override
public AuthenticationInfo afterAttempt(Realm realm,
AuthenticationToken token, AuthenticationInfo singleRealmInfo,
AuthenticationInfo aggregateInfo, Throwable t)
throws AuthenticationException {
if ("myRealm2".equals(realm.getName())) {
if ((singleRealmInfo == null)
|| (singleRealmInfo.getPrincipals() == null)) {
throw new AuthenticationException("策略:myRealm2 认证不通过");
}
}
return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
}
}
config.ini
[main]
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
#配置认证策略
authcStrategy = com.itmuch.shiro.MyAuthenticationStrategy
authenticator.authenticationStrategy = $authcStrategy
myRealm1 = com.itmuch.shiro.MyRealm1
myRealm2 = com.itmuch.shiro.MyRealm2
authenticator.realms = $myRealm2,$myRealm1
securityManager.authenticator = $authenticator
test
public class Test {
public static void main(String[] args) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(
"classpath:config.ini");
SecurityManager manager = factory.createInstance();
SecurityUtils.setSecurityManager(manager);
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cccc");
token.setRememberMe(true);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
System.out.println(currentUser.isPermitted("p1"));
}
}
5、注解授权代码
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="com.itmuch.shiro"></context:component-scan>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="realm" ref="myRealm" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
<bean id="myRealm" class="org.apache.shiro.realm.text.IniRealm">
<property name="resourcePath"
value="F:/Eclipse_Workspaces/projects/140505/Shiro_0500_Authorization_Annotation/src/config.ini"></property>
</bean>
</beans>
config.ini
[main]
[users]
user1=cc,role1
[roles]
role1="p1:create,update:u123",p2
test
@Service
public class Test {
@Autowired
private org.apache.shiro.mgt.SecurityManager securityManager;
// @RequiresPermissions({ "p1" }) // 报错,因为没有该权限
// @RequiresGuest // 报错,因为已经登陆了
// @RequiresUser //可以通过
// @RequiresRoles({ "role2" }) // 报错,因为只有role1
@RequiresAuthentication
@RequiresPermissions({ "p1:create:u123" }) // 可以通过
/**
* 特别注意:被注解授权的方法,必须是public的,否则压根注入不进来,注解写了等于没写.
* 好大一个坑,浪费了1个小时,备注下。
*/
public void afterLogin() {
System.out.println("afterLogin is ok.");
}
@RequiresUser // 报错,因为还没登陆
public void beforeLogin() {
System.out.println("beforeLogin is ok.");
}
public void login() {
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cc");
token.setRememberMe(true);
SecurityUtils.setSecurityManager(this.securityManager);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Test test = (Test) context.getBean("test");
test.beforeLogin();
test.login();
test.afterLogin();
}
}
6、自定义单个 realm,用 annotation 授权,纯 spring 配置,不使用 ini 配置方式)密码加密
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--开启注解-->
<context:component-scan base-package="com.itmuch.shiro"></context:component-scan>
<!--配置Bean生存周期-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="realm" ref="myRealm"></property>
<property name="sessionManager" ref="sessionManager" />
</bean>
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
</bean>
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
<bean id="myRealm" class="com.itmuch.shiro.MyRealm">
<property name="credentialsMatcher" ref="sha256Matcher"></property>
</bean>
<!-- 设置密码匹配器,注意:形如org.apache.shiro.authc.credential.Sha256CredentialsMatcher 类似这种方式的配置已经不推
荐使用了 -->
<!-- 以下是推荐的配置方式,即:统一用HashedCredentialsMatcher,然后指定编码 -->
<bean id="sha256Matcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 编码符合Java Security Framework标准,用到的时候百度吧 -->
<property name="hashAlgorithmName" value="SHA-256"></property>
<property name="storedCredentialsHexEncoded" value="false"></property>
</bean>
</beans>
MyRealm.java
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> s = new HashSet<String>();
s.add("p1");
s.add("p2");
info.setStringPermissions(s);
Set<String> r = new HashSet<String>();
r.add("r1");
r.add("r2");
info.setRoles(r);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getUsername(), new
Sha256Hash(token.getPassword()).toBase64().toCharArray(), this.getName());
return info;
}
}
test
@Service
public class Test {
@Autowired
private org.apache.shiro.mgt.SecurityManager securityManager;
@RequiresAuthentication
@RequiresRoles("r1")
public void afterLogin() {
System.out.println("afterLogin is ok.");
}
public void login() {
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cc");
token.setRememberMe(true);
SecurityUtils.setSecurityManager(this.securityManager);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Test test = (Test) context.getBean("test");
test.login();
test.afterLogin();
}
}
密码加盐
application.xml
<bean id="sha256Matcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 编码符合Java Security Framework标准,用到的时候百度吧 -->
<property name="hashAlgorithmName" value="SHA-256"></property>
<property name="storedCredentialsHexEncoded" value="false"></property>
<!-- 加入盐的时候的配置,加密过后,再迭代十次,使密码更加安全 -->
<property name="hashIterations" value="10"></property>
</bean>
MyRealm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//第三个参数为盐
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, new Sha256Hash(token.getPassword(),
username, 10).toBase64().toCharArray(),
ByteSource.Util.bytes(username), this.getName());
return info;
}
7、JdbcRealm,直接连接数据库验证,代码写的比较死不好改造,实际开发中意义不大
8、Session 管理
自定义 session 监听器、使用默认的 sessionDAO
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="com.itmuch.shiro"></context:component-scan>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="sessionManager" ref="sessionManager" />
<property name="realm" ref="myRealm"></property>
</bean>
<bean id="myRealm" class="com.itmuch.shiro.MyRealm2" />
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
<!-- 以下为session相关配置 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<property name="sessionListeners">
<list>
<bean id="mySessionListener" class="com.itmuch.shiro.MySessionListener" />
</list>
</property>
<!-- session被创建或更新时,它的数据需要持久化到一个存储位置以便它能够被稍后的应用程序访问,SessionDAO实现了这个功能
-->
<property name="sessionDAO" ref="sessionDAO"></property>
</bean>
<!-- 如下配置后,将会把session数据放入ehCache中 -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"></bean>
</beans>
MySessionListener
// 实战用途不大
public class MySessionListener implements SessionListener {
@Override
public void onStart(Session session) {
System.out.println("Session - onStart");
}
@Override
public void onStop(Session session) {
System.out.println("Session - onStop");
}
@Override
public void onExpiration(Session session) {
System.out.println("Session - onExpiration");
}
}
test
@Service
public class Test {
@Autowired
private org.apache.shiro.mgt.SecurityManager securityManager;
public void afterLogin(Subject currentUser) {
Session session = currentUser.getSession();
session.setAttribute("user", "11111");
System.out.println(session.getAttribute("user"));
}
public Subject login() {
UsernamePasswordToken token = new UsernamePasswordToken("user1", "cc");
token.setRememberMe(true);
SecurityUtils.setSecurityManager(this.securityManager);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
return currentUser;
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Test test = (Test) context.getBean("test");
Subject currentUser = test.login();
test.afterLogin(currentUser);
}
}
自定义 sessionDAO
application
<!-- 如下配置后,将会把session数据放入ehCache中 -->
<bean id="sessionDAO" class="com.itmuch.shiro.MySessionDAO"></bean>
MySessionDAO (可以改用raids代替缓存)
public class MySessionDAO extends AbstractSessionDAO {
private final Map<Serializable, Session> sessions = new HashMap<Serializable, Session>();
@Override
public void update(Session session) throws UnknownSessionException {
System.out.println("session update.");
this.sessions.put(session.getId(), session);
}
@Override
public void delete(Session session) {
System.out.println("session delete.");
this.sessions.remove(session.getId());
}
@Override
public Collection<Session> getActiveSessions() {
System.out.println("session getActiveSessions.");
return this.sessions.values();
}
@Override
protected Serializable doCreate(Session session) {
System.out.println("session doCreate.");
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.sessions.put(sessionId, session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
System.out.println("session doReadSession.");
return this.sessions.get(sessionId);
}
}