Shiro 核心 API

Subject:用户主体(每次请求都会创建Subject)。

	principal:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。

	credential:代表凭证。常见的有密码,数字证书等。

SecurityManager:安全管理器(关联 Realm),用于安全校验。

Realm:Shiro 连接数据的桥梁。

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。

CacheManager:缓存控制器,来管理如用户、角色、权限等缓存的控制器。

SessionManager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中。

SessionDAO:会话储存。

Shiro 认证与授权

身份认证:

	Step1:应用程序代码调用 Subject.login(token) 方法后,传入代表最终用户身份的 AuthenticationToken 实例 Token。

	Step2:将 Subject 实例委托给应用程序的 SecurityManager(Shiro 的安全管理)并开始实际的认证工作。

	Step3、4、5:SecurityManager 根据具体的 Realm 进行安全认证。

权限认证(涉及到三张表:用户表、角色表和权限表):

	权限(Permission):即操作资源的权利(添加、修改、删除、查看操作的权利)。

	角色(Role):指的是用户担任的角色,一个角色可以有多个权限。

	用户(User):在 Shiro 中,代表访问系统的用户,即上面提到的 Subject 认证主体。

请求步骤:

	浏览器发出第一次请求的时候,去redis里找不到对应的session,会进入到登录页。

	当在登录页输入完正确的账号密码后,才能登录成功。

	根据登录成功后的session生成sessionId,并传到前端浏览器中,浏览器以cookie存储,同时将session存储到redis中。

	每次浏览器访问后台,都会刷新session的过期时间expireTime。

	当浏览器再次请求时,将当前浏览器中的所有的cookie设置到request headers请求头中。
根据传入的sessionId串到共享的redis存储中匹配。
如果匹配不到,则会跳转到登录页,如果匹配成功,则会访问通过。

Spring Boot Shiro 依赖

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</dependency>

自定义 Realm

自定义 Realm 需要继承 AuthorizingRealm 类,该类封装了很多方法,且继承自 Realm 类。

重写以下两个方法:
doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。 public class CustomRealm extends AuthorizingRealm { @Autowired
private UserService userService; /**
* 登录成功的用户授予权限和分配角色
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取用户名
String account = (String) principals.getPrimaryPrincipal(); //从数据库查询用户角色信息
User user = userService.getUserByAccount(account); //设置角色
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet();
if (user.getAdmin()) {
roles.add(Base.ROLE_ADMIN);
}
authorizationInfo.setRoles(roles); return authorizationInfo;
} /**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户名
String account = (String) token.getPrincipal(); //从数据库查询该用户
User user = userService.getUserByAccount(account); if (null == user) {
throw new UnknownAccountException(); //没找到该帐号
}
if (UserStatus.blocked.equals(user.getStatus())) {
throw new LockedAccountException(); //帐号锁定
} //传入用户名和密码进行身份认证,并返回认证信息
return new SimpleAuthenticationInfo(
user.getAccount(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), //盐(密码加盐加密处理)
getName()
);
}
}

自定义 SessionDAO

Cachemanager缓存里可以包含权限认证的缓存、用户及权限信息的缓存等,也可以做Session缓存。

SessionDAO是做Session持久化的,可以使用Redis来存储。

默认 SessionDAO:MemorySessionDAO:

	将Session保存在内存中,存储结构是ConcurrentHashMap。

	public class MemorySessionDAO extends AbstractSessionDAO {

	    private ConcurrentMap<Serializable, Session> sessions = new ConcurrentHashMap();

	    protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.storeSession(sessionId, session);
return sessionId;
}
} /**
* 将Session保存到Redis
*/
public class CustomSessionDAO extends CachingSessionDAO { @Autowired
private RedisTemplate redisTemplate; //默认缓存过期时间:30分钟
public final static long DEFAULT_EXPIRE = 60 * 30; @Override
protected Serializable doCreate(Session session) {
//创造SessionId
Serializable sessionId = generateSessionId(session);
//注册SessionId
assignSessionId(session, sessionId);
//缓存Session
redisTemplate.opsForValue().set(sessionId.toString(), session, DEFAULT_EXPIRE, TimeUnit.SECONDS);
return sessionId;
} @Override
protected void doUpdate(Session session) {
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
//会话过期/停止
return;
}
redisTemplate.opsForValue().set(session.getId().toString(), session, DEFAULT_EXPIRE, TimeUnit.SECONDS);
} @Override
protected void doDelete(Session session) {
redisTemplate.delete(session.getId().toString());
} @Override
protected Session doReadSession(Serializable sessionId) {
return (Session) redisTemplate.opsForValue().get(sessionId.toString());
}
}

自定义 SessionManager(待完善)

public class CustomSessionManager extends DefaultWebSessionManager {

    public static final String TOKEN = "token";

    /**
* 调用登陆接口的时候,是没有token的。
* 登陆成功后,产生了token,我们把它放到request中。
* 返回结果给客户端的时候,把它从request中取出来,并且传递给客户端。
* 客户端每次带着这个token过来,就相当于是浏览器的cookie的作用,也就能维护会话了。
*/
@Override
public Serializable getSessionId(SessionKey key) {
Serializable sessionId = key.getSessionId();
if(sessionId == null && WebUtils.isWeb(key)){
HttpServletRequest request = WebUtils.getHttpRequest(key);
HttpServletResponse response = WebUtils.getHttpResponse(key);
sessionId = this.getSessionId(request,response);
}
HttpServletRequest request = WebUtils.getHttpRequest(key);
request.setAttribute(TOKEN,sessionId.toString());
return sessionId;
} /**
* DefaultWebSessionManager默认实现中,是通过Cookie确定SessionId。
* 重写时,只需要把获取SessionId的方式变更为在request header中获取即可。
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String id = httpRequest.getHeader(TOKEN); if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
return super.getSessionId(request, response);
}
}

配置类 ShiroConfig

@Configuration
public class ShiroConfig { /**
* 创建 ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); //LinkedHashMap 是有序的,进行顺序拦截器配置
Map<String, String> filterMap = new LinkedHashMap();
filterMap.put("/static/**", "anon"); //无需认证可以访问
filterMap.put("/login", "anon");
filterMap.put("/register", "anon"); //配置退出过滤器,其中具体的退出代码Shiro已经替我们实现了,登出后跳转配置的LoginUrl
filterMap.put("/logout", "logout"); filterMap.put("/**/create", "authc"); //必须认证才可以访问
filterMap.put("/**/update", "authc");
filterMap.put("/**/delete", "authc");
filterMap.put("/upload", "authc"); filterMap.put("/admin", "perms[admin]"); //资源必须得到资源权限才能访问,多个参数写法:perms["admin,user"]
filterMap.put("/admin", "role[admin]"); //资源必须得到角色权限才能访问 filterMap.put("/**", "anon"); //设置默认登录的URL,身份认证失败会访问该URL
shiroFilterFactoryBean.setLoginUrl("/login");
//身份认证设置成功之后要跳转的URL
shiroFilterFactoryBean.setSuccessUrl("/index");
//设置未授权界面,权限认证失败会访问该URL
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorized"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
} /**
* 配置安全管理器:DefaultWebSecurityManager
*/
@Bean
public SecurityManager securityManager(CustomRealm realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
} /**
* 创建自定义 Realm
*/
@Bean
public CustomRealm customRealm() {
CustomRealm shiroRealm = new CustomRealm();
//配置密码加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5"); //加密方式
matcher.setHashIterations(2); //加密次数
shiroRealm.setCredentialsMatcher(matcher);
return shiroRealm;
} /**
* 配置 SessionManager
*/
@Bean
public SessionManager sessionManager() {
CustomSessionManager customSessionManager = new CustomSessionManager();
//session过期时间:1小时(默认半小时)
customSessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
customSessionManager.setSessionDAO(new CustomSessionDAO());
return customSessionManager;
} /**
* Spring Boot Shiro 开启注释
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}

Session的查询、刷新

SimpleSession几个属性:

	Serializable id:session id;

	Date startTimestamp:session的创建时间;

	Date stopTimestamp:session的失效时间;

	Date lastAccessTime:session的最近一次访问时间,初始值是startTimestamp

	long timeout:session的有效时长,默认30分钟

	boolean expired:session是否到期

	Map<Object, Object> attributes:session的属性容器

查询:Session session = SecurityUtils.getSubject().getSession();	//返回的就是绑定在当前subjuct的session。

刷新:SimpleSession的touch()

	public void touch() {
this.lastAccessTime = new Date();
} Web应用,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间。 过期时间判断:当前时间-lastAccessTime=是否超过有效时长。

Shiro支持三种方式的授权

1、编程式,通过写if/else授权代码块

	Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
// 有权限,执行相关业务
} else {
// 无权限,给相关提示
} 2、注解式,通过在执行的Java方法上放置相应的注解完成 @RequiresPermissions("admin")
public List<User> listUser() {
// 有权限,获取数据
} 3、JSP/GSP标签,在JSP/GSP页面通过相应的标签完成 <shiro:hasRole name="admin">
<!-- 有权限 -->
</shiro:hasRole>

Spring Boot 集成 Shiro 和 Ehcache(待完善)

引入配置文件 ehcache.xml:

	application.xml配置文件添加:spring.cache.ehcache.config=classpath:ehcache.xml

	<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false"> <diskStore path="java.io.tmpdir/shiro-cache"/> <defaultCache //默认缓存策略
eternal="false" //对象是否永久有效,一但设置了,timeout将不起作用
maxElementsInMemory="1000" //缓存最大元素数目
overflowToDisk="false" //当内存中对象数量达到maxElementsInMemory时,Ehcache将对象写到磁盘中。
diskPersistent="false" //是否在磁盘上持久化。指重启JVM后,数据是否有效。默认为false
timeToIdleSeconds="0" //设置对象在失效前的允许闲置时间(单位:s),默认是0(永久有效)。
timeToLiveSeconds="600" //设置对象在失效前允许存活时间(单位:s),默认是0(永久有效)。
//当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存
//LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)
memoryStoreEvictionPolicy="LRU" /> <cache
name="users" //缓存名称
eternal="false"
maxElementsInMemory="500"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU" />
</ehcache>

Controller 与 Service 使用

Controller:

    @PostMapping("/login")
public Result login(@RequestBody User user) {
Result r = new Result();
//获取Subject用户主体
Subject subject = SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(), user.getPassword());
try {
//执行认证操作(调用UserRealm中的方法认证)
subject.login(token);
//认证通过
User currentUser = userService.getUserByAccount(user.getAccount());
subject.getSession().setAttribute(Base.CURRENT_USER, currentUser);
r.setResultCode(ResultCode.SUCCESS);
r.getData().put("token", subject.getSession().getId());
} catch (UnknownAccountException e) {
r.setResultCode(ResultCode.USER_NOT_EXIST); //用户不存在
} catch (LockedAccountException e) {
r.setResultCode(ResultCode.USER_ACCOUNT_FORBIDDEN); //账号被锁定
}catch (IncorrectCredentialsException e) {
r.setResultCode(ResultCode.USER_LOGIN_PASSWORD_ERROR); //密码错误
} catch (AuthenticationException e) {
r.setResultCode(ResultCode.USER_LOGIN_ERROR); //认证错误(包含以上错误)
}
return r;
} @PostMapping("/register")
public Result register(@RequestBody User user) {
Result r = new Result(); User temp = userService.getUserByAccount(user.getAccount());
if (null != temp) {
r.setResultCode(ResultCode.USER_HAS_EXISTED);
return r;
} userService.saveUser(user); r.setResultCode(ResultCode.SUCCESS);
return r;
} Service: @Override
@Transactional
public void saveUser(User user) {
//密码加密
String newPassword = new SimpleHash(
"md5",user.getPassword(),
ByteSource.Util.bytes("salt"),2).toHex(); user.setPassword(newPassword); return userRepository.save(user);
}

Spring Boot Shiro的更多相关文章

  1. (39.4) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    在读此文章之前您还可能需要先了解: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...

  2. (39.3) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    在学习此小节之前您可能还需要学习: (39.1) Spring Boot Shiro权限管理[从零开始学Spring Boot] http://412887952-qq-com.iteye.com/b ...

  3. (39.2). Spring Boot Shiro权限管理【从零开始学Spring Boot】

    (本节提供源代码,在最下面可以下载) (4). 集成Shiro 进行用户授权 在看此小节前,您可能需要先看: http://412887952-qq-com.iteye.com/blog/229973 ...

  4. (39.1) Spring Boot Shiro权限管理【从零开始学Spring Boot】

    (本节提供源代码,在最下面可以下载)距上一个章节过了二个星期了,最近时间也是比较紧,一直没有时间可以写博客,今天难得有点时间,就说说Spring Boot如何集成Shiro吧.这个章节会比较复杂,牵涉 ...

  5. Spring Boot Shiro 权限管理

    Spring Boot Shiro 权限管理 标签: springshiro 2016-01-14 23:44 94587人阅读 评论(60) 收藏 举报 .embody{ padding:10px ...

  6. spring boot shiro redis整合基于角色和权限的安全管理-Java编程

    一.概述 本博客主要讲解spring boot整合Apache的shiro框架,实现基于角色的安全访问控制或者基于权限的访问安全控制,其中还使用到分布式缓存redis进行用户认证信息的缓存,减少数据库 ...

  7. Spring Boot Shiro 使用教程

    Apache Shiro 已经大名鼎鼎,搞 Java 的没有不知道的,这类似于 .Net 中的身份验证 form 认证.跟 .net core 中的认证授权策略基本是一样的.当然都不知道也没有关系,因 ...

  8. 十、 Spring Boot Shiro 权限管理

    使用Shiro之前用在spring MVC中,是通过XML文件进行配置. 将Shiro应用到Spring Boot中,本地已经完成了SpringBoot使用Shiro的实例,将配置方法共享一下. 先简 ...

  9. Spring Boot Shiro 权限管理 【转】

    http://blog.csdn.net/catoop/article/details/50520958 主要用于备忘 本来是打算接着写关于数据库方面,集成MyBatis的,刚好赶上朋友问到Shiro ...

随机推荐

  1. ctrl+r 调用bash曾经的历史命令

    在bash界面 按ctrl+r 可以调出, bash中曾经的历史命令, 光标会停留在 第一次被匹配的字符上, (即使后面你再输入被匹配的字符, 光标也不移动) 然后, 根据你的需要 来进行任何一次的操 ...

  2. oracle 11g不能导出空表的解决方法

    在oracle 11g r2中,发现传统的exp居然不能导出空的表,然后查询一下,  发现需要如下的步骤去搞,笔记之.    oracle 11g 新增了一个参数:deferred_segment_c ...

  3. nginx坑记录

    问题1: 配置解析过程使用ngx_cycle->pool申请内存保存配置,结果造成野指针. 背景:需求开发过程,有一些结构需要在配置解析阶段保存,然后可以动态修改.看原来的代码配置解析都是使用c ...

  4. [转载]X509证书中RSA公钥的提取与载入 pem key

    原地址:https://blog.csdn.net/anddy926/article/details/8940377 由于项目需要,我计划利用openssl开发一个基本的CA,实现证书的发放等功能.在 ...

  5. 浅谈Java反射机制 之 获取类的字节码文件 Class.forName("全路径名") 、getClass()、class

    另一个篇:获取 类 的 方法 和 属性(包括构造函数) 先贴上Java反射机制的概念: AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它 ...

  6. 安装gradle和配置

    1:官网下载地址:https://docs.gradle.org/current/userguide/installation.html 下载自己认为的版本(压缩包) 2:解压到目标目录 3:配置gr ...

  7. New start-开始我的学习记录吧

    不知道从何说起,就从眼下的感想开始吧. 转行是一件不容易的事情! 今天是来北京学习Java的第41天.小测验了两次,一次51分,一次54分. 下午有学长过来分享了他的成长经历,感触很多.不是灌鸡汤,也 ...

  8. 一次特殊的“VARCHAR转numeric失败”错误记录

    今天接触到一个很有意思的问题.当我在执行一条INSERT的sql语句时,他总是报字符串转数字类型失败. 问题 首先,该表中的所有数字类型的字段都是非必填,其次,每个数字类型的字段都有默认值.最令我感到 ...

  9. mysql 小数位

    1    select convert(t/100,decimal(15,2)) as a from user (1) convert() 一.在mysql操作中我们经常需要对数据进行类型转换.此时我 ...

  10. MySQL-第十一篇JDBC典型用法

    1.JDBC常用方式      1>DriverManager:管理JDBC驱动的服务类.主要用于获取Connection.其主要包含的方法: public static synchronize ...