Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是
Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所
有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有
Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以
把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身
份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/
权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
内部结构框架
Shiro中的shiro.ini说明
shiro.ini放置在classpath路径下shiro会自动查找。
ini配置文件中有四大主要配置类
main users roles urls
测试
@Test
public void te1(){
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.通过Factory对象获取SecurityManager对象
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager对象添加到当前运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 4.获取Subject对象
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken("root", "123456");
// 登录操作
subject.login(token);
// 获取登录的状态
System.out.println(subject.isAuthenticated());
}
//UnknownAccountException 账号错误
//IncorrectCredentialsException//密码错误
shiro.ini
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
[roles]
admin = *
schwartz = lightsaber:*
goodguy = user:delete:zhangsan
认证:
ini 配置
[main]
#自定义 realm
customRealm=com.ecpss.demo.Myrealm
#将realm设置到securityManager
securityManager.realms=$customRealm
public class Myrealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//只认证账号 密码自动认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 这里是更具userName从数据库查询
if(!"root".equals(username))
return null;
String pass="123456";
return new SimpleAuthenticationInfo(username,pass,"myrealm");
}
}
加密
使用MD5存在一个问题,相同的password生成的hash值是相同的,如果两个用户设置了相同的密码,那么数据库
中会存储两个相同的值,这是极不安全的,加Salt可以在一定程度上解决这一问题,所谓的加Salt方法,就是加
点‘佐料’。其基本想法是这样的,当用户首次提供密码时(通常是注册时)由系统自动往这个密码里撒一些‘佐
料’,然后在散列,而当用户登录时,系统为用户提供的代码上撒上相同的‘佐料’,然后散列,再比较散列值,来
确定密码是否正确。
加盐的原理:
给原文加入随机数生成新的MD5的值
注册的时候需要将salt 保存在数据库中--可以用uuid 的随机值--然后可以通过数据库进行查询
授权
授权,又称作为访问控制,是对资源的访问管理的过程,即对于认证通过的用户,授予他可以访问某些资源的权限。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName =(String) principals.getPrimaryPrincipal();
// 更具账号去数据库中查询出所有的角色和权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 添加角色
info.addRole("xc");
// 添加权限
info.addStringPermission("xc:update");
return info;
}
拦截器ShiroFilterFactoryBean
为什么要使用多realm认证?
实际开发中存在这样一种场景,同一个密码可能在MqSQL中存储,也可能在Oracle中存储,有可能MqSQL中使用的是MD5加密算法,而Oracle使用SHA1加密算法。这就需要有多个Realm以及认证策略的问题。
为什么要使用缓存
在没有使用缓存的情况下,我们每次发送请求都会调用一次doGetAuthorizationInfo方法来进行用户的授权操作,但是我们知道,一个用户具有的权限一般不会频繁的修改,也就是每次授权的内容都是一样的,所以我们希望在用户登录成功的第一次授权成功后将用户的权限保存在缓存中,下一次请求授权的话就直接从缓存中获取,这样效率会更高一些。
整合ehcache和springboot
ehcache.xml 文件放在resource 下
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="java.io.tmpdir"/>
<cache name="users"
timeToLiveSeconds="300"
maxEntriesLocalHeap="1000"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
/**
* 缓存管理器
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
/**
* 设置为共享模式
* @return
*/
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
cacheManagerFactoryBean.setShared(true);
return cacheManagerFactoryBean;
}
//创建DefaultWebSecurityManager
@Bean(name = "sm")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("my") MyRealm myRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myRealm);
defaultWebSecurityManager.setCacheManager(ehCacheManager());
return defaultWebSecurityManager;
}
清空缓存
在自定义realm中添加清空方法
/**
* 清空缓存
*/
public void clearCache(){
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
session
session
shiro提供的session不依赖web容器,可以直接使用,如果是在web环境下,session中的数据和httpsession
中的数据是通的。Shiro中的session可以出现在任何地方,例如service、dao等,不需要从controller中传递
session参数,用户保存在session中的数据可以在HTTP session中获取,保存在httpsession中的数据也可以
从session中获取。
使用
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("msg", "自定义传递的信息...");
remeber me
anon 例子/admins/**=anon #没有参数,表示可以匿名使用。
authc 例如/admins/user/**=authc #表示需要认证(登录)才能使用,没有参数
roles 例子/admins/user/**=roles[admin], #参数可以写多个,多个时必须加上引号,并且参数之间用逗
号分割,当有多个参数时,例如admins/user/**=roles[“admin,guest”], 每个参数通过才算通过,相当于
hasAllRoles()方法。
perms 例子/admins/user/**=perms[user:add:*], #参数可以写多个,多个时必须加上引号,并且参数之间
用逗号分割,例如/admins/user/**=perms[“user:add:*,user:modify:*”],当有多个参数时必须每个参数都
通过才通过,想当于isPermitedAll()方法。
rest 例子/admins/user/**=rest[user], #根据请求的方法,相当于/admins/user/
port 例子/admins/user/**=port[8081], #当请求的url的端口不是8081是跳转到
schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的
host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
authcBasic 例如/admins/user/**=authcBasic #没有参数表示httpBasic认证
ssl 例子/admins/user/**=ssl #没有参数,表示安全的url请求,协议为https
user 例如/admins/user/**=user #没有参数表示必须存在用户,当登入操作时不做检查
集成spring
1.加入依赖
2.在web.xml配置shiroFilter
<!--
1. 配置 Shiro 的 shiroFilter.
2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和
<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.
-->
<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>
3.在applicationContext.xml配置
DelegatingFilterProxy 作用是自动到 Spring 容器查找名 字为 shiroFilter(filter-name)的 bean 并
把所有 Filter 的操作委托给它。
认证
1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
1). 创建一个表单页面
2). 把请求提交到 SpringMVC 的 Handler
3). 获取用户名和密码.
4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法.
6. 由 shiro 完成对密码的比对.
<bean id="jdbcRealm" class="com.ecpss.shiro.realms.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
//加密并且指定加密算法md5(进行md5盐值加密)
public static void main(String[] args) {
String hashAlgorithmName = "SHA1";
Object credentials = "123456";
Object salt = ByteSource.Util.bytes("admin");
int hashIterations = 10;//加密次数
Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(result);//加密十次后的结果
}
授权,也叫访问控制,即在应用中控制谁访问哪些资源(authorization)
session
在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个
session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的
session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服
务。
---------------------------------------------------------------------------------------
Spring Security OAuth2
什么是 oAuth
oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的
授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请
获得该用户资源的授权,因此 oAuth 是安全的。
名词解释
第三方应用程序(Third-party application): 又称之为客户端(client),比如上节中提到的设备(PC、
Android、iPhone、TV、Watch),我们会在这些设备中安装我们自己研发的 APP。又比如我们的产品想要使用
QQ、微信等第三方登录。对我们的产品来说,QQ、微信登录是第三方登录系统。我们又需要第三方登录系统的资源
(头像、昵称等)。对于 QQ、微信等系统我们又是第三方应用程序。
HTTP 服务提供商(HTTP service): 我们的云笔记产品以及 QQ、微信等都可以称之为“服务提供商”。
资源所有者(Resource Owner): 又称之为用户(user)。
用户代理(User Agent): 比如浏览器,代替用户去访问这些资源。
认证服务器(Authorization server): 即服务提供商专门用来处理认证的服务器,简单点说就是登录功能
(验证用户的账号密码是否正确以及分配相应的权限)
资源服务器(Resource server): 即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一
台服务器,也可以是不同的服务器。简单点说就是资源的访问入口,比如上节中提到的“云笔记服务”和“云相册服
务”都可以称之为资源服务器。
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-
oauth2/src/test/resources/schema.sql
官方提供的表结构
Spring security
一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进
行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认
证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户
名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。
在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以
进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring
security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。
一个类继承WebSecurityConfigurerAdapter
可以定制我们的方法(覆盖)
授权
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
withUser("admin").
password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
}
//AuthenticationManagerBuilder可以很容易的创建一个AuthenticationManager
很容易就构造出在内存中进行认证。
//AuthenticationManager 处理一个Authentication请求
/**
Authentication authenticate(Authentication authentication)
**/
//ProviderManager是AuthenticationManager的实现类
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表单登录
.and()
.authorizeRequests()//请求授权
.anyRequest()
.authenticated();//任何请求都要认证
}
基本原理
过滤器链
UsernamePasswordAuthenticationFilter//处理用户表单登录的默认使用/login,post请求
-------------------------------
从数据库中读出数据进行验证,不用内存
UserDetailsService
@Service
public class AuthenticService implements UserDetailsService{
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println(username+"----------------------");
return new User(username,bCryptPasswordEncoder.encode("admin"),
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
}//必须加上ROLE_
}