Shiro是什么
• Apache Shiro 是 Java 的一个安全(权限)框架。 (spring中提供的是springsecurity)
• Shiro 可以非常容易的开发出足够好的应用,其不仅可以用JavaSE 环境,也可以用在 JavaEE 环境。
• Shiro 可以完成:认证(登录判断)、授权(把用户的权限授予用户)、加密、会话管理、与Web 集成、缓存 等。
• 下载地址:http://shiro.apache.org/
基本功能
具体核心介绍
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web 支持,可以非常容易的集成到Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
Shiro 外部架构
从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成
架构图介绍
Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外API 核心就是 Subject。Subject 代表了当前“用户”,与 Subject 的所有交互都会委托给 SecurityManager;
SecurityManager:安全管理器;即所有与安全有关的操作(负责进行认证、授权、会话及缓存的管理)都会与SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro的核心, 它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色
Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
内部架构图
Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。
初步认识(maven-java project)
pom.xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc 和web项目整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
Quickstart.java
package com.tf;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
//使用shiro.ini文件初始化工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到当前用户对象
Subject currentUser = SecurityUtils.getSubject();
//会话
/* Session session = currentUser.getSession();
session.setAttribute("name", "xiaopang");
String value = (String) session.getAttribute("name");
if (value.equals("xiaopang")) {
log.info("name"+value);
}*/
//当前用户认证?
if (!currentUser.isAuthenticated()) {
//将用户名和密码封装为一个UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken("admin", "111");
//记住功能 web工程可以用到
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("账户不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("密码错误" + token.getPrincipal() );
} catch (LockedAccountException lae) {
log.info("账户被冻结 " + token.getPrincipal() );
}
catch (AuthenticationException ae) {
log.info("未知异常!");
}
}
log.info("User " + currentUser.getPrincipal() );
//具有role?
if (currentUser.hasRole("ceo")) {
log.info("具有ceo角色");
} else {
log.info("不具有ceo角色");
}
if (currentUser.hasRole("admin")) {
log.info("具有admin角色");
} else {
log.info("不具有admin角色");
}
//具有权限?
if (currentUser.isPermitted("sys:user:list")) {
log.info("具有sys:user:list");
} else {
log.info("不具有sys:user:list");
}
//退出功能 web工程用到
currentUser.logout();
System.exit(0);
}
}
shiro.ini
# ----------------用户-------------------------------------------------------------
[users]
# 定义一个root的用户 密码secret 角色:admin
root = secret, admin
admin =111, admin
# 用户:lonestarr 密码:vespa 角色:goodguy和schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------角色权限------------------------------------------------------------
[roles]
# admin用户具有所有权限
admin = *
# goodguy 具有sys:user下的所有权限
goodguy = sys:user:*
# schwartz 具有sys:user:delete的权限
schwartz = sys:user:delete
# 后期这个数据应该从数据库查找
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
进一步学习
初步认识后,我们会发现用户信息如果定义在配置文件ini中,不是太理想,而应该定义到数据库中。
接下来我们可以先来模拟一下数据库的操作。需要自定义一个realm。
##自定义一个Realm类
① 编写Realm类继承AuthorizingRealm类
package com.tf.shiro02;
import org.apache.shiro.authc.*;
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 java.util.ArrayList;
import java.util.List;
/*
* shiro涉及最少5张表
* 用户表 角色表 权限表
* 用户角色表 角色权限表
* */
public class MyRealm extends AuthorizingRealm {
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权===========");
//把当前用户的角色和权限查询出来
// 通过shiro提供的api授权处理即可
// AuthorizationInfo 的一个子接口SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模拟数据库的数据
List<String> roles = new ArrayList<String >();
roles.add("admin");
roles.add("ceo");
roles.add("PD");
List<String> permissions = new ArrayList<String>();
permissions.add("admin");
permissions.add("sys:user:add");
permissions.add("sys:user:update");
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
//认证 主要是用来 比对 用户名和密码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证===========");
String username = (String)authenticationToken.getPrincipal();
String password = new String((char[])authenticationToken.getCredentials());
//调用service层
//模拟service
String uname= "admin";
String pwd = "jbgsn";
if(username.equals(username)){
if(password.equals(pwd)){
AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}else{
throw new IncorrectCredentialsException("密码不存在!");
}
}else{
throw new UnknownError("账户不存在!");
}
}
}
②:shiro02.ini
#自定义Realm的全路径
customRealm=com.tf.shiro02.MyRealm
securityManager.realms=$customRealm
③: TestShiro02
package com.tf.shiro02;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro02 {
public static void main(String[] args){
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro02.ini");
//import org.apache.shiro.mgt.SecurityManager; 不要导错包了
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currrentUser = SecurityUtils.getSubject();
//用户认证?
if(!currrentUser.isAuthenticated()){
//用户名和密码以后可以从数据库里面获取
UsernamePasswordToken token = new UsernamePasswordToken("admin","jbgsn");
try{
currrentUser.login(token);
}catch (UnknownAccountException uae) {
System.out.println("账户不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
System.out.println("密码错误" + token.getCredentials() );
} catch (LockedAccountException lae) {
System.out.println("账户被冻结 " + token.getPrincipal() );
}
catch (AuthenticationException ae) {
System.out.println("未知异常!");
}
//角色判断
if(currrentUser.hasRole("ceo")){
System.out.println("具有ceo角色");
}else{
System.out.println("不具有ceo角色");
}
//权限
if(currrentUser.isPermitted("sys:user:add")){
System.out.println("具有sys:user:add权限");
}else{
System.out.println("不具有sys:user:add权限");
}
}else{
System.out.println("认证成功,可以登陆了");
}
}
}
结果:
授权===========
具有ceo角色
授权===========
具有sys:user:add权限
结果会发现调用了两次授权,正常情况其实调用一次就行了,这是因为没有用到缓存,后面和spring的整合可以加上这个缓存.
加密功能
shiro提供了加密功能(MD5Hash类)
package com.tf.shiro02;
import org.apache.shiro.crypto.hash.Md5Hash;
public class Md5 {
public static void main(String[] args){
Md5Hash md5Hash1 = new Md5Hash("jbgsn","admin");
Md5Hash md5Hash2 = new Md5Hash("jbgsn","admin",1024);
System.out.println(md5Hash1);
System.out.println(md5Hash2);
}
}
密码 :jbgsn 盐值:admin(尽量是唯一的,比如:当前用户名) 加密次数:1024
加密的结果是32位
结果:
027dd732a22adc14c5e1900b7087ade9
7a85bf432ebb5bf9f96f755ba4ad6450
shiro和SSM整合(web project)
参照下载的 shiro 源码中的samples\spring 配置web.xml 文件和 Spring 的配置文件
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--加载spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:app*.xml</param-value>
</context-param>
<!--springMVC的配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--先拦截所有路径,再考虑放行-->
<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>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
• DelegatingFilterProxy 作用是自动到 Spring 容器查找名字为 shiroFilter(filter-name)的 bean 并把所有 Filter
的操作委托给它。
URL 匹配顺序
• URL 权限采取第一次匹配优先的方式,即从头开始
使用第一个匹配的 url 模式对应的拦截器链。
• 如:
– /bb/=filter1
– /bb/aa=filter2
– /=filter3
– 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹
配,那么将使用 filter1 进行拦截。
applicationContext.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.xsd">
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 缓存 -->
<property name="cacheManager" ref="cacheManager"/>
<!--自定义realm-->
<property name="realm" ref="MyRealm"/>
</bean>
<!--缓存管理-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!--自定义realm-->
<bean id="MyRealm" class="com.tf.shiro02.MyRealm">
</bean>
<!--shiro注解在spring相关类中生效 -->
<!--生命周期-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<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>
<!--id :shiroFilter必须和web.xml中的DelegatingFilterProxy的servlet-name的id一致-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录页面-->
<property name="loginUrl" value="/login.jsp"/>
<!--登录成功后的页面-->
<property name="successUrl" value="/success.jsp"/>
<!--没有权限的页面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--自定义设置-->
<!--anon: 匿名访问
authc:认证后访问
切记有先后顺序
-->
<property name="filterChainDefinitions">
<value>
/favicon.ico = anon
/login = anon
/logout = anon
/css**=anon
# allow WebStart to pull the jars for the swing app:
/*.jar = anon
# everything else requires authentication:
/** = authc
</value>
</property>
</bean>
</beans>
ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir/shiro-spring-sample"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
<cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="600"
overflowToDisk="false"/>
</ehcache>
springmvc-servlet.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--扫描controller包-->
<context:component-scan base-package="com.tf.controller"></context:component-scan>
<!-- 注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!--放行静态资源-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- shiro注解在spring相关类中生效
@RequiresRoles("Manager") //具有Manager角色才能访问
如果在service层使用这些注解,则配置到spring配置文件中即可
但,如果在controller中使用注解,需要把这几个配置配置到springmvc的配置文件中
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<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>
</beans>
MyController
package com.tf.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
@RequestMapping("/index")
public String show(){
return "main";
}
@RequestMapping("/login")
public String login() {
//模拟
String username = "admin";
String password = "jbgsn";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
currentUser.login(usernamePasswordToken);
//自动调用Realm 进行授权
return "main";
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
}
MyRealm
package com.tf.shiro02;
import org.apache.shiro.authc.*;
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 java.util.ArrayList;
import java.util.List;
/*
* shiro涉及最少5张表
* 用户表 角色表 权限表
* 用户角色表 角色权限表
* */
public class MyRealm extends AuthorizingRealm {
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权===========");
//把当前用户的角色和权限(菜单表或资源表)查询出来
// 通过shiro提供的api授权处理即可
// AuthorizationInfo 的一个子接口SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模拟数据库的数据
List<String> roles = new ArrayList<String >();
roles.add("admin");
roles.add("ceo");
roles.add("PD");
List<String> permissions = new ArrayList<String>();
permissions.add("admin");
permissions.add("sys:user:add");
permissions.add("sys:user:update");
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
//认证 主要是用来 比对 用户名和密码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证===========");
//用户输入的用户名和密码
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
String password =new String(token.getPassword());
//调用service层
//模拟service
String uname= "admin";
String pwd = "jbgsn";
if(username.equals(uname)){
if(password.equals(pwd)){
/*
* 参数一:user对象 ,这里先因为没有封装对象,先传入username
* 参数二:密码
* 参数三:自定义realm的名字
* */
AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}else{
throw new IncorrectCredentialsException("密码不存在!");
}
}else{
throw new UnknownError("账户不存在!");
}
}
}
验证结果:
一:先访问/index 是不能访问到main.jsp
二:应该先访问/login 才能访问到main.jsp
注意:如果这两步做颠倒了,需要先清理一下浏览器的缓存,再测试
授权
授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。
Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
有三种方式可以实现
一:编程式
@RequestMapping("/login")
public String login(){
String username = "admin";
String password = "jbgsn";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// token.setRememberMe(true);//记住我功能 会话区间,直接能访问成功界面
try{
//subject.isRemembered();记住我
subject.login(token);//自定义realm
System.out.println(currentUser.hasRole("admin")?"具有admin角色":"不具有admin角色");
System.out.println(currentUser.hasRole("PM")?"具有PM角色":"不具有PM角色");
System.out.println(currentUser.hasRole("CEO")?"具有CEO角色":"不具有CEO角色");
System.out.println(currentUser.isPermitted("sys:menu:add")?"具有新增菜单权限":"不具有菜单新增权限");
System.out.println(currentUser.isPermitted("sys:menu:list")?"具有查询菜单权限":"不具有查询菜单权限");
System.out.println(currentUser.isPermitted("sys:order:add")?"具有新增订单权限":"不具有新增订单权限");
return "redirect:index";
}catch(Exception e){
System.out.println(e.getMessage());
return null;
}
}
这个显然很麻烦,而且可移植性不好,故不提倡使用
二:注解式
/**
* 判断权限的第二种方式 注解式
* @return
*/
@RequiresRoles("Manager") //具有Manager角色才能访问
@RequestMapping("/index")
public String index(){
return "main";
}
@RequiresRoles("admin") //具有admin角色才能访问
@RequestMapping("/index1")
public String index1(){
return "main";
}
@RequiresPermissions({"sys:menu:list","sys:user:list"})
@RequestMapping("/index2")
public String index2(){
return "main";
}
但是记得:
<!-- shiro注解在spring相关类中生效
@RequiresRoles("Manager") //具有Manager角色才能访问
如果在service层使用这些注解,则配置到spring配置文件中即可
但,如果在controller中使用注解,需要把这几个配置配置到springmvc的配置文件中
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<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>
第三种:使用shiro提供的标签
main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--第一步导入标签库 只能再jsp中使用--%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<shiro:principal></shiro:principal>你好!
<shiro:hasRole name="admin">
具有admin角色
</shiro:hasRole>
<shiro:hasPermission name="sys:user:add">
添加用户
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:delete">
删除用户
</shiro:hasPermission>
</body>
</html>
Shiro 标签
• Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。
guest 标签
用户没有身份验证时显示相应信息,即游客访问信息:
user 标签:
用户已经经过认证/记住我登录后显示相应的信息。
authenticated 标签:
用户已经身份验证通过,即记住我
Subject.login登录成功,不是记住我登录的
notAuthenticated 标签:
用户未进行身份验证,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
pincipal 标签:
显示用户身份信息,默认调用
Subject.getPrincipal() 获取,即 Primary Principal。
hasRole 标签:
如果当前Subject有任意一个角色(或的关系)将显示body体内容。
hasAnyRoles 标签:
如果当前Subject有任意一个角色(或的关系)将显示body体内容。
lacksRole:
如果当前 Subject 没有角色将显示 body 体内容
hasPermission:
如果当前 Subject 有权限将显示 body 体内容
lacksPermission:
如果当前Subject没有权限将显示body体内容。
权限注解
@RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true
@RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。
@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
user:b。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
user:b。
默认拦截器
Shiro 内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举
身份验证相关的
授权相关的
接下来我们再来梳理一下
认证流程
流程
身份验证
身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个
Primary principals,一般是用户名/邮箱/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的 principals 和 credentials 组合就是用户名/密码了
身份验证基本流程
1、收集用户身份/凭证,即如用户名/密码
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户
错误信息;否则登录成功
3、创建自定义的 Realm 类,继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法
AuthenticationException
如果身份验证失败请捕获 AuthenticationException 或其子类
最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
Realm
Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作
Realm 的继承关系:
一般继承 AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。
Authenticator
Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 异常
SecurityManager 接口继承了 Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行
验证,验证规则通过 AuthenticationStrategy 接口指定
AuthenticationStrategy
realm 的认证方式
AuthenticationStrategy 接口的默认实现:
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略
授权流程
流程如下:
1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;
3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,
如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示授权失败。
ModularRealmAuthorizer
ModularRealmAuthorizer 进行多 Realm 匹配流程:
1、首先检查相应的 Realm 是否实现了实现了Authorizer;
2、如果实现了 Authorizer,那么接着调用其相应的isPermitted*/hasRole* 接口进行匹配;
3、如果有一个Realm匹配那么将返回 true,否则返回 false。