学习Spring Boot:(十三)配置 Shiro 权限认证

经过前面学习 Apache Shiro ,现在结合 Spring Boot 使用在项目里,进行相关配置。

正文

添加依赖

pom.xml 文件中添加 shiro-spring 的依赖:

        <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version></dependency>

RBAC

RBAC 1 是基于角色的访问控制,权限与角色关联,给用户配置相关角色,来获取权限信息。

Shiro 配置

新建一个新的 Shiro 配置类 ShiroConfig:

package com.wuwii.common.config;import com.wuwii.module.sys.autho2.OAuth2Realm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;
import java.util.Map;/*** Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。* 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,* 所以我们需要定义一系列关于URL的规则和访问权限。** @author KronChan* @version 1.0* @since <pre>2018/2/9 10:35</pre>*/
@Configuration
public class ShiroConfig {@Beanpublic SessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionValidationSchedulerEnabled(true);sessionManager.setSessionIdCookieEnabled(true);return sessionManager;}/*** 注册安全管理,必须设置 SecurityManager** @param oAuth2Realm    认证* @param sessionManager 缓存* @return*/@Beanpublic SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 可以添加多个认证,执行顺序是有影响的securityManager.setRealm(oAuth2Realm);securityManager.setSessionManager(sessionManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);//自定义一个oauth2拦截器,不设置就是使用默认的拦截器/*Map<String, Filter> filters = new HashMap<>();filters.put("oauth2", new OAuth2Filter());shiroFilter.setFilters(filters);*///拦截器//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->Map<String, String> filterMap = new LinkedHashMap<>();//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了filterMap.put("/sys/logout", "logout");// 验证码filterMap.put("/sys/captcha.jpg", "anon");// 设置系统模块下访问需要权限filterMap.put("/sys/login", "anon");// 自定义的拦截//filterMap.put("/sys/**", "oauth2");filterMap.put("/sys/**", "authc");// 登陆的 urlshiroFilter.setLoginUrl("/sys/login");// 登陆成功跳转的 urlshiroFilter.setSuccessUrl("/");// 未授权的 url// shiroFilter.setUnauthorizedUrl("/login.html");//未授权界面;shiroFilter.setUnauthorizedUrl("/403");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}/*** Shiro生命周期处理器* @return*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 开启Shiro的注解,* (如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,* 并在必要时进行安全逻辑验证 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能** @return*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();proxyCreator.setProxyTargetClass(true);return proxyCreator;}/*** 开启 shiro aop注解支持.** @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}/*** 凭证匹配器* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码;* )*  <b>需要在身份认证中添加 realm.setCredentialsMatcher(hashedCredentialsMatcher())</b>* @return*//*@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));return hashedCredentialsMatcher;}*/
}

Filter Chain定义说明:

  1. 一个URL可以配置多个Filter,使用逗号分隔
  2. 当设置多个过滤器时,全部验证通过,才视为通过
  3. 部分过滤器可指定参数,如perms,roles

Shiro内置的FilterChain:

Filter NameClass
anonorg.apache.shiro.web.filter.authc.AnonymousFilter
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter
sslorg.apache.shiro.web.filter.authz.SslFilter
userorg.apache.shiro.web.filter.authc.UserFilter

* anon:所有url都都可以匿名访问
* authc: 需要认证才能进行访问
* user:配置记住我或认证通过可以访问

自定义的拦截器(可选)

如果需要按照自己的需要定义一个 oauth2 的拦截器,则需要 继承 AuthenticatingFilter 实现几个方法即可。

/*** oauth2过滤器*/
public class OAuth2Filter extends AuthenticatingFilter {/*** logger*/private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class);@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {//获取请求tokenString token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {return null;}return new OAuth2Token(token);}/***  shiro权限拦截核心方法 返回true允许访问resource,* @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return false;}/*** 当访问拒绝时是否已经处理了;* 如果返回true表示需要继续处理;* 如果返回false表示该拦截器实例已经处理完成了,将直接返回即可。* @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {//获取请求token,如果token不存在,直接返回401String token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {HttpServletResponse httpResponse = (HttpServletResponse) response;((HttpServletResponse) response).setStatus(401);response.getWriter().print("没有权限,请联系管理员授权");return false;}return executeLogin(request, response);}/*** 鉴定失败,返回错误信息* @param token* @param e* @param request* @param response* @return*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {try {((HttpServletResponse) response).setStatus(401);response.getWriter().print("没有权限,请联系管理员授权");} catch (IOException e1) {LOGGER.error(e1.getMessage(), e1);}return false;}/*** 获取请求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//从header中获取tokenString token = httpRequest.getHeader("token");//如果header中不存在token,则从参数中获取tokenif (StringUtils.isBlank(token)) {return httpRequest.getParameter("token");}// 还可以实现从 cookie 获取Cookie[] cookies = httpRequest.getCookies();if(null==cookies||cookies.length==0){return null;}for (Cookie cookie : cookies) {if (cookie.getName().equals("token")) {token = cookie.getValue();continue;}}return token;}
}

具体实现可以参考我的上篇文章 《Apache Shiro的拦截器和认证》

认证实现

Shiro的认证过程最终会交由Realm执行,这时会调用Realm的 getAuthenticationInfo(token) 方法。
该方法主要执行以下操作:

  1. 检查提交的进行认证的令牌信息
  2. 根据令牌信息从数据源(通常为数据库)中获取用户信息
  3. 对用户信息进行匹配验证。
  4. 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
  5. 验证失败则抛出AuthenticationException异常信息。

而在我们的应用程序中要做的就是自定义一个Realm类,继承 AuthorizingRealm 抽象类,重载 doGetAuthenticationInfo (),重写获取用户信息的方法。

@Component
public class OAuth2Realm extends AuthorizingRealm {@Resourceprivate ShiroService shiroService;@Resourceprivate SysUserService sysUserService;/*** 此方法调用  hasRole,hasPermission的时候才会进行回调.** 权限信息.(授权):* 1、如果用户正常退出,缓存自动清空;* 2、如果用户非正常退出,缓存自动清空;* 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。* (需要手动编程进行实现;放在service进行调用)* 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,* 调用clearCached方法;* :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。*** 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,* 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;* 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,* 缓存过期之后会再次执行。** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SysUserEntity user =(SysUserEntity) (principals.getPrimaryPrincipal());;// 获取该用户权限列表Set<String> permsSet = shiroService.getUserPermissions(user.getId());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.setStringPermissions(permsSet);return info;}/*** 认证回调函数,登录时调用* 首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;* 如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,* 交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;SysUserEntity user = sysUserService.queryByUsername(usernamePasswordToken.getUsername());//账号不存在、密码错误if (user == null) {throw new KCException("账号或密码不正确");}// 交给 shiro 自己去验证,// 明文验证return new SimpleAuthenticationInfo(user, // 存入凭证的信息,登陆成功后可以使用 SecurityUtils.getSubject().getPrincipal();在任何地方使用它user.getPassword(),getName());// 加密的方式// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现/*return new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()), // 加盐,可以注册凭证匹配器 HashedCredentialsMatcher 告诉它怎么加密的getName());*/}
}

实现上面两个方法即可完成身份验证和权限验证。

登陆实现

    @PostMapping("/login")@ApiOperation("系统登陆")public ResponseEntity<String> login(@RequestBody SysUserLoginForm userForm) {String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);if (!userForm.getCaptcha().equalsIgnoreCase(kaptcha)) {throw new KCException("验证码不正确!");}UsernamePasswordToken token = new UsernamePasswordToken(userForm.getUsername(), userForm.getPassword());Subject currentUser = SecurityUtils.getSubject();currentUser.login(token);//账号锁定if (getUser().getStatus() == SysConstant.SysUserStatus.LOCK) {throw new KCException("账号已被锁定,请联系管理员");}return ResponseEntity.status(HttpStatus.OK).body("登陆成功!");}

权限验证

    @ApiOperation("用于测试,查询")@ApiImplicitParam(name = "string", value = "id", dataType = "string")@RequiresPermissions("sys:user:list1")@GetMapping()public ResponseEntity<List<SysUserEntity>> query(@CustomValid String string) {return new ResponseEntity<>(sysUserService.query(new SysUserEntity()), OK);}

简单测试一个例子,sys:user:list1 多加一个 1 肯定会验证失败,查看程序会做什么,它会去我们定义的 Realm 中的 doGetAuthorizationInfo(PrincipalCollection principals) 方法中,执行查询该用户的所有权限。
验证失败后最后程序结果如下:

Caused by: org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public org.springframework.http.ResponseEntity com.wuwii.module.sys.controller.SysUserController.query(java.lang.String)at org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(AuthorizingAnnotationMethodInterceptor.java:90)... 77 common frames omitted
权限注解
@RequiresAuthentication  
表示当前Subject已经通过login进行了身份验证;即Subject. isAuthenticated()返回true@RequiresUser  
表示当前Subject已经身份验证或者通过记住我登录的。@RequiresGuest  
表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)  
表示当前Subject需要角色admin和user。@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)  
表示当前Subject需要权限user:a或user:b。  

参考资料

  • [Spring Boot 集成-Shiro]
  • 39.2. Spring Boot Shiro权限管理【从零开始学Spring Boot】

  1. Role-Based Access Control ?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php设计之初用于什么,PHP设计模式(七)之门面模式

一、什么是外观模式(Facade Pattern)定义&#xff1a;外观模式又称门面模式&#xff0c;提供一个统一的接口&#xff0c;用来访问子系统中的一群接口。外部与子系统之间的通信采用门面(Facade)对象来完成。【举例】比如麦当劳套餐&#xff0c;套餐包含鸡肉卷、汉堡包、可乐等N个…

学习Spring Boot:(十四)spring-shiro的密码加密

前言 前面配置了怎么使用 shiro &#xff0c;这次研究下怎么使用spring shiro的密码加密&#xff0c;并且需要在新增、更新用户的时候&#xff0c;实现生成盐&#xff0c;加密后的密码进行入库操作。 正文 配置凭证匹配器 Beanpublic HashedCredentialsMatcher hashedCreden…

php 仿高德,仿高德路线规划滑动效果

因为项目有个界面要模仿高德地图路径规划滑动效果&#xff0c;因此写了demo&#xff0c;并简单说下分析过程。高德地图效果演示:仿高德路线规划滑动.gifdemo效果演示:高德地图规划滑动.gif一. 分析首先&#xff0c;我们可以看出这个滚动的视图应该是UIScrollView或者UIScrollVi…

php验证码完整功能,PHP验证码功能的实现

/***产生验证码图片*/public function actionVerfiycode() {Header ( "Content-type: image/gif" );$border 0; //是否要边框 1要:0不要$how 4; //验证码位数$w $how * 15; //图片宽度$h 20; //图片高度$fontsize 5; //字体大小$alpha "abcdefghijkmnopqr…

学习Spring Boot:(十五)使用Lombok来优雅的编码

前言 Lombok 是一种 Java? 实用工具&#xff0c;可用来帮助开发人员消除 Java 的冗长&#xff0c;尤其是对于简单的 Java 对象&#xff08;POJO&#xff09;。它通过注解实现这一目的。 正文 添加依赖 在 pom.xml 文件中添加相关依赖&#xff1a; <lombok.version>1.…

java 品尝饮料,java细节经典题型

28. 选项中哪一行代码可以替换题目中//add code here 而不产生编译错误?() [java] view plaincopy 1. public abstract class MyClass { 2. 3. 4. 5.......Java 基础试题 一:选择题(1*3030) (题目写在答题纸上面) 1:Java 提供哪几种运算符多选 ( abcd )。 A)算术运算符 B)位运…

学习Spring Boot:(十六)使用Shiro与JWT 实现认证服务

前言 代码可以参考 需要把Web应用做成无状态的&#xff0c;即服务器端无状态&#xff0c;就是说服务器端不会存储像会话这种东西&#xff0c;而是每次请求时access_token进行资源访问。这里我们将使用 JWT 1&#xff0c;基于散列的消息认证码&#xff0c;使用一个密钥和一个消…

java泛型和注解,泛型 · 注解和泛型 · 看云

[TOC]# 泛型## 为什么要使用泛型在之前学过的集合框架中&#xff0c;List和Map都使用了泛型技术来确认其内容的数据类型。如果不使用泛型&#xff0c;在程序运行阶段&#xff0c;会带来数据类型转型的错误风险。~~~List list new ArrayList();list.add("tom");for (…

java程序单引号报错,javapoigetInpuStream报错br/是这样的, 爱问知识人

是这样的&#xff0c;我写了重载了两个getInputStream方法&#xff0c;当调用不带参数的方法时&#xff0c;运行正常&#xff0c;当调用带参的方法则报 Can not find a java.io.InputStream with the name [inputStream] in是这样的&#xff0c;我写了重载了两个getInputStream…

学习Spring Boot:(十七)Spring Boot 中使用 Redis

前言 Redis是一个由Salvatore Sanfilippo写的key-value存储系统。 edis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 通常被称为数据结构服务器&#xff0c;因为值&#xff08;va…

jqueryd登录异步请求 java,ajaxd的js和jquery实现

先来看一下javascript的。var httpxml;httpxml new XMLHttpRequest();httpxml.onreadystatechangefunction(){ //当服务器响应就绪时执行函数(就是说服务器准备好了你可以发请求了)if(httpxml.status 200 && httpxml.readyState 4){//这里200和4代表响应的状态&…

学习Spring Boot:(十八)Spring Boot 中session共享

前言 前面我们将 Redis 集成到工程中来了&#xff0c;现在需要用它来做点实事了。这次为了解决分布式系统中的 session 共享的问题&#xff0c;将 session 托管到 Redis。 正文 引入依赖 除了上篇文章中引入 spring-boot-starter-data-redis&#xff0c;还需要 spring-sess…

matlab 码元扩展,扩频通信及matlab仿真

扩展频谱通信以及直接扩频的matlab仿真号无关)扩展频谱后成为宽频带信号&#xff0c;然后再进行传输的一种系统。待传输的基带信号就是信源发出的数字信号。特定的扩频函数通常选用各种伪随机序列(扩频码)&#xff0c;其码元传输速率远大于基带信号速率&#xff0c;因而和基带信…

学习Spring Boot:(十九)Shiro 中使用缓存

前言 在 shiro 中每次去拦截请求进行权限认证的时候&#xff0c;都会去数据库查询该用户的所有权限信息&#xff0c; 这个时候就是有一个问题了&#xff0c;因为用户的权限信息在短时间内是不可变的&#xff0c;每次查询出来的数据其实都是重复数据&#xff0c;没必要每次都去…

matlab安装无效距离过远,求助matlab的远程序

求助matlab的远程序function varargout a1(varargin)% A1 M-file for a1.fig% A1, by itself, creates a new A1 or raises the existing% singleton*.%% H A1 returns the handle to a new A1 or the handle to% the existing singleton*.%% A1(CALLBACK,hObject,eventData,…

学习Spring Boot:(二十)使用 MongoDB

前言 MongoDB&#xff08;来自于英文单词“Humongous”&#xff0c;中文含义为“庞大” &#xff09;是可以应用于各种规模的企业、各个行业以及各类应用程序的开源数据库。基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoD…

php 事件调度,PHP单元测试调度事件

如何在函数调用期间测试事件是否被调度&#xff1f;public function updateUser() {//Do some update stuff$event new UserUpdated($user);$event->attach([new SendEmailAddressChangeEmail($emailAddress),new SendEmailAddressChangeEmail($oldEmailAddress),]);$event…

学习Spring Boot:(二十一)使用 EhCache 实现数据缓存

前言 当多次查询数据库影响到系统性能的时候&#xff0c;可以考虑使用缓存&#xff0c;来解决数据访问新能的问题。 SpringBoot 已经为我们提供了自动配置多个 CacheManager 的实现&#xff0c;只要去实现使用它就可以了。 一般的系统都是优先使用 EhCache&#xff0c;它工作…

php如何解决报错,php 启动报错如何解决_PHP教程

复制代码 代码如下:[rootabc lnmp]# service php-fpm startStarting php-fpm eAccelerator: Could not allocate 67108864 bytes, the maximum size the kernel allows is 33554432 bytes. Lower the amount of memory request or increase the limit in /proc/sys/kernel/shmm…

学习Spring Boot:(二十二)使用 AOP

前言 AOP&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。基于AOP实现的功能不会破坏原来程序逻辑&#xff0c;因此它可以很好的对业务逻辑的各个部分进行隔离&#xff0c;从而使得业务逻辑各部分之间的…