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. bash如何向上向下移动历史命令记录

    bash如何在 历史命令记录 中上下移动? 通过bash本身的设置,就可以做到! : 用shift+up , shift+down 来上下移动 : 单纯的up/down是调用历史命令

  2. flask_sqlalchemy获取动态 model名称 和 动态查询

    需求 想要实现动态的查询,表名,字段,字段值都不是固定的 obj=表名.query.filter_by(字段=值1).first() obj.字段=值2 首先动态获取db_model名字(即Role) ...

  3. 测开之路一百一十:bootstrap图片

    bootstrap图片 引入bootstrap 原版的图片 bootstrap处理后的: 圆角.圆形.缩略图 自适应窗口

  4. 类BigDecimal

    /* * float和double类型的数据存储和int不一样 * * float和double类型容易丢失精度 * * 因此使用BigDecimal * * BigDecimal类描述:不可变的,任 ...

  5. Temporal-Difference Control: SARSA and Q-Learning

    SARSA SARSA algorithm also estimate Action-Value functions rather than State-Value function. The dif ...

  6. Haystack Python全文检索框架

    Haystack 1.什么是Haystack Haystack是django的开源全文搜索框架(全文检索不同于特定字段的模糊查询,使用全文检索的效率更高 ),该框架支持Solr,Elasticsear ...

  7. 实现atoi

    1. 去掉首位空格 2. 判断首位是否有正负号 3. 判断各位是否是0~9,有其他字符直接返回当前结果   public class Solution { public int atoi(String ...

  8. Flask 中请求钩子的理解和应用?

    请求钩子是通过装饰器的形式实现的,支持以下四种:1,before_first_request 在处理第一个请求前运行2,before_request:在每次请求前运行3,after_request:如 ...

  9. vi编辑器没有颜色的解决办法

    Centos里的VI只默认安装了vim-minimal-7.x.所以无论是输入vi或者vim查看文件,syntax功能都无法正常启用.因此需要用yum安装另外两个组件:vim-common-7.x和v ...

  10. C#取模的理解:为什么当a<b,a%b=a?

    一,取模a%b 1,如果a>b,例如10%7=3,这是什么原因呢?可以根据下面的理解 10 =7*1+3,则模就是3 2,如果a<b,例如7%10 = 7,这时怎么得到的呢?根据下面来理解 ...