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. Oracle redo与undo浅析

    http://liwenshui322.iteye.com/blog/1488949 一. 什么是redo(用于前滚数据) redo也就是重做日志文件(redo log file),Oracle维护着 ...

  2. 关于 token

    用户在浏览器做一系列操作,后台服务怎么判断这些操作是来自同一个用户? 1. seesion 用户登录后,后台生成 sessionid 返回给浏览器,浏览器的每次请求带上 sessionid,后台关联 ...

  3. 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_06 Set集合_7_可变参数

    打印出来是一个数组的地址 打印数组的长度 只要调用了add方法就会创建一各长度为0的数组 传一个10过去.就输出了数组的长度为1 传两个数字 注意事项: 特殊写法

  4. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_04 IO字节流_7_字节输出流的续写和换行

    再执行一次.内容会追加在后面 换行 不同系统下的换行符号   每次都写上换行符号,在for循环里面,循环10次

  5. Jmeter JDBC请求-----数据库读取数据进行参数化 通过SSH跳板机连接数据库

    前期准备: jdbc驱动:mysql-connector-java-5.1.7-bin.jar Jmeter 要链接MySQL数据库,首选需要下载mysql jdbc驱动包(注:驱动包的版本一定要与你 ...

  6. 应用安全 - 路由器 - D-LINK - 漏洞汇总

    D-Link D-Link DSL-2750B任意命令执行漏洞 CVE-2019-16920 影响范围 DIR- DIR-866L DIR- DHP- CVE-2017-7405 Date 类型 嗅探 ...

  7. python列表-增强的赋值操作

    增强赋值公式 (1) (2) (3) (4)

  8. Java-Lambda表达式第二篇认识Lambda表达式

    接上面的方法引用和构造器引用: 3>引用某类对象的实例方法 @FunctionalInterface public interface Cut{ String cut(String str,in ...

  9. nginx 499错误

    原因: 服务响应时间太长,客户端自动断开链接. 解决: 1. 找到响应世间长的接口,看依赖的数据源(数据库,第三方接口等)响应时间是否超时,还是自己程序有逻辑问题. 可以通过加入日志打印时间消耗来确定 ...

  10. Oracle数据库的特点与工作原理

    Oracle数据库的特点 1.开放性: Oracle能在所有主流平台上运行(包括Windows),完全支持所有的工业标准,采用完全开放策略,可以使客户选择最适合的解决方案,对开发商全力支持. 2.可伸 ...