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. JS - 创建只读属性

    一:为私有变量创建get()方法 这种方式可以创建 "伪 "只读属性.这并不是一种好方法,因为使用_函数_获得只读的_属性_不太符合一般的逻辑. /** * Represent a ...

  2. IncSecond:将一个TDateTime变量加减一定数量的秒数

    http://tieba.baidu.com/p/1998083296 IncSecond:将一个TDateTime变量加减一定数量的秒数 声明:function IncSecond ( const ...

  3. 没看这篇干货,别和我说你会IDEA Debug

    所谓工欲善其事必先利其器,现在idea已经成为java开发者眼中最热门最好用的IDE了.下面这篇文章将总结下idea调试的一些高级技巧. 多线程调试 直接上例子说明,比如下面这段代码 debug模式下 ...

  4. 【SD系列】SAP 创建销售订单-用外部给号的方法步骤

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[SD系列]SAP 创建销售订单-用外部给号的方 ...

  5. 【ABAP系列】SAP ABAP 生成随机数的函数

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP ABAP 生成随机数的函数 ...

  6. git的忽略文件语法规范

    忽略文件语法规范 空行或是以 # 开头的行即注释行将被忽略. 可以在前面添加正斜杠 / 忽略当前路径文件,但不包括子目录的同名文件. 可以在后面添加正斜杠 / 来忽略文件夹. 可以使用 ! 来否定忽略 ...

  7. gradle使用方法

    创建一个项目 你可以通过创建一个build.gradle的文件来开始一个项目,然后可以向文件中加入构建逻辑. $ mkdir basic-demo $ cd basic-demo $ touch bu ...

  8. .NET MVC Json()处理大数据异常解决方法

    [1-部分原文]: .NET MVC Json()处理大数据异常解决方法 整个项目采用微软的ASP.NET MVC3进行开发,前端显示采用EasyUI框架,图表的显示用的是Highcharts,主要进 ...

  9. Ansible--常用命令整理

    由于最近使用ansible在多台服务器部署程序,运行命令的时候,发现对Linux和ansible自动运维工具用的不太熟练,所以搜集整理一些,方便日后复习提升,达到熟练运用的目的. 对于详细的安装教程和 ...

  10. LED音乐频谱之点阵

    转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/37967455 一.硬件 watermark/2/text/aHR0cDovL2 ...