目录
4.2实现UserRealm内的doGetAuthenticationInfo(登录认证)方法
5.2实现UserRealm内的doGetAuthorizationInfo(资源授权认证)方法
本文是在SpringBoot框架下整合shiro,
利用Thymeleaf(SpringBoot推荐使用的页面模板,在HTML基础上添加了一些特定模板)
进行shiro登录与权限相关实现
一,前期准备
1,SpringBoot框架搭建
这个就不多做阐述,可以利用idea的Spring Initializr来快速构建一个SpringBoot项目
2.引入Thymeleaf页面模板
注:我本次是结合了springBoot推荐的Thymeleaf模板引擎,进行前后端不分离的springBoot+shiro整合的,如果不使用Thymeleaf,则可忽略当前模块
<!--*注:thymeleaf3.0版本以前对标签语法要求较为严格,开始标签必须有对应的标签
如果希望页面语法不必过于严谨,可将thymeleaf升级为3.0及以上版本-->
<properties>
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
</properties>
<!--thymeleaf页面(springBoot框架推荐使用的页面模板,在HTML基础上添加了一些特定模板)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在src/main/resources目录下,创建固定目录名:templates
在templates下,创建所需的页面
如果位置写错,或目录名写错,在后面从controller跳转页面时,会报错
二、SpringBoot与Shiro整合
1.引入jar
<!--shrio-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.定义Realm类
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* Created with IntelliJ IDEA
* @Description: 自定义Realm
**/
public class UserRealm extends AuthorizingRealm{
/**
* @Description: 执行授权逻辑
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权==========================");
return null;
}
/**
* @Description: 执行认证逻辑操作
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证==========================");
return null;
}
}
自定义Realm类,
1.继承AuthorizingRealm
2.实现AuthorizingRealm的doGetAuthorizationInfo(授权)与doGetAuthenticationInfo(认证)两个方法
3.这个两个方法,在后面进行授权与认证相关逻辑时会实现具体方法
3.定义Shiro配置类
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: 自定义shiro配置
**/
@Configuration
public class ShiroConfig {
/**
* @Description: 创建ShiroFilterFactoryBean
**/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//shiro内置的过滤器
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
*Shiro内置过滤器,可实现权限相关拦截器
* changy9ong过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证(登录)才可访问
* user:如果使用rememberMe的功能可直接访问
* perms:该资源必须得到资源授权才可访问
* role:该资源必须得到角色权限才可访问
**/
Map<String,String> filterMap = new LinkedHashMap<>();
//认证拦截
/*filterMap.put("/add","authc");
filterMap.put("/update","authc");*///指定具体路径
//无需认证
filterMap.put("/test","anon");
filterMap.put("/login","anon");
//授权拦截
//*注:"user:add"字符串可以自定义
filterMap.put("/add","perms[user:add]");
filterMap.put("/update","perms[user:update]");
//认证拦截
filterMap.put("/*","authc");//利用通配符,对指定目录下进行全部拦截
shiroFilterFactoryBean.setLoginUrl("/tologin");//设置跳转的登录页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");//设置跳转的未授权页面
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//*注意:使用了LinkedHashMap,进行了顺序存入,所以,filterMap存入是有先后顺序之分的!!!
//我目前的路径顺序:排除路径—>授权路径—>拦截路径
return shiroFilterFactoryBean;
}
/**
* @Description: 创建DefaultWebSecurityManager
**/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//管理realm
securityManager.setRealm(realm);
return securityManager;
}
/**
* @Description: 创建Realm
**/
@Bean(name = "userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
4.实现登录认证
4.1登录controller
@RequestMapping(value = "/login")
public String login(String name,String password,Model model){
/**
*使用shiro编写认证操作
**/
//1.获取Subject
Subject subject = SecurityUtils.getSubject();
//2.封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
//3.执行登录方法
try {
subject.login(token);
return "/test";
}catch (UnknownAccountException e){//shiro封装的Exception
model.addAttribute("msg","用户名不存在");
return "/login";
}catch (IncorrectCredentialsException e){//shiro封装的Exception
model.addAttribute("msg","密码错误");
return "/login";
}
}
4.2实现UserRealm内的doGetAuthenticationInfo(登录认证)方法
@Autowired
private EntityService entityService;
/**
* @Description: 执行认证逻辑操作
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证==========================");
//实现shiro认证逻辑
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
Entity entity = entityService.gtEntityByName(token.getUsername());
if (entity==null){
return null;//shiro会爬出UnknownAccountException
}
//1).principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
Object principal = entity;
//2).Object credentials:密码
Object credentials = entity.getPassword();
//3).realmName:当前realm对象的name,调用父类的getName()方法即可
String realmName = getName();
return new SimpleAuthenticationInfo(principal,credentials,realmName);
}
4.3ShiroConfig的配置
在getShiroFilterFactoryBean配置内,配置需要登录认证的路径、跳转登录页面的路径
在上面的ShiroConfig内已经配置,可参阅
4.4登录认证流程
输入账号密码
——>
Controller的Login接口接收账号密码
将账号密码传入shiro的UsernamePasswordToken
——>
UserRealm的doGetAuthenticationInfo()方法,authenticationToken接收账号密码信息
(1.)通过账号查询用户信息,未查询到,返回null,shiro内部会抛出UnknownAccountException异常
(2.)如查询到用户信息,无需判断密码,将查询出的用户信息内的密码返回,shiro会内部判断,如果不与传入的密码不同,则会抛出IncorrectCredentialsException异常
5.实现资源授权认证
5.1ShiroConfig的配置
在getShiroFilterFactoryBean配置内,配置需要授权的路径、跳转授权提示页面路径
在上面的ShiroConfig内已经配置,可参阅
5.2实现UserRealm内的doGetAuthorizationInfo(资源授权认证)方法
@Autowired
private EntityService entityService;
/**
* @Description: 执行授权逻辑
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权==========================");
//资源授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
Entity entity = (Entity)subject.getPrincipal();
Entity dbEntity = entityService.selectByPrimaryKey(entity.getId());
info.addStringPermission(dbEntity.getPerms());
return info;
}
5.3资源授权认证流程
1.shiroconfig内配置需要授权的路径与自定义的字符
//*注:"user:add"字符串可以自定义 filterMap.put("/lvjia/carbodyad/add","perms[user:add]"); filterMap.put("/lvjia/carbodyad/update","perms[user:update]");
在授权认证时,会匹配当前访问路径是否具有该自定义的权限名
2.UserRealm的doGetAuthorizationInfo()方法是进行授权认证的方法
获取到登录认证时传入的用户信息对象,
将用户资源权限信息交给SimpleAuthorizationInfo进行资源授权认证
3.如果资源授权认证通过,则跳转到资源路径
否则跳转到已配置的资源认证提醒路径
*注:shiro注解方式拦截问题
我们可以在controller方法上加
@RequiresPermissions("user:add")注解,进行权限验证拦截,
@RequiresRoles("admin")注解,进行角色拦截
在spring/springBoot项目内,只添加注解还不够,还需要下面两步配置(以SpringBoot为例,在shiroConfig内,Spring是在XML配置文件内):
1.开启AOP,对类的代理
/** * @Description: 开启AOP **/ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true);//对类的代理 return advisorAutoProxyCreator; }
开启shiro注解支持
/** * @Description: 开启Shiro注解支持 **/ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
2.定义需要特殊处理的异常
配置这个原因:使用了注解进行权限/角色拦截认证后,如果无权限,应该会跳转到无权限的相应页面路径(在上面的shiroConfig内已经配置)。
但是会发现,并没有跳转到相应的页面/路径,而是直接抛出了无权限的异常。
这是因为shiro还没来得及捕获异常,就被spring大妈处理了....所以我们有两种方式可以解决,
方法一、配置一个SimpleMappingExceptionResolver,来捕获特殊的异常,在里面进行异常处理
/** * @Description: 定义需要特殊处理的异常,用类名或完全路径名作为Key,异常页面/接口地址为值 * 解决Shiro注解拦截后直接抛异常,未来得及被shiro处理跳转指定路径的问题 **/ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){ SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); properties.setProperty("org.apache.shiro.authz.AuthorizationException","redirect:/lvjia/carbodyad/noAuth"); properties.setProperty("java.lang.Throwable","redirect:/lvjia/carbodyad/noAuth");//所有的异常都从属Throwable,配置了Throwable,所有的异常都会被捕获,处理 simpleMappingExceptionResolver.setExceptionMappings(properties); return simpleMappingExceptionResolver; }
方法二、使用@RestControllerAdvice / @ControllerAdvice注解定义一个异常捕获的controller,在controller内进行处理
//@RestControllerAdvice // 返回前端json @ControllerAdvice // 跳转到指定的页面资源 public class ExceptionController { // 捕捉 CustomRealm 抛出的异常 AuthorizationException:可以自定义异常类 @ExceptionHandler(AuthorizationException.class) public String handleShiroException(AuthorizationException ex) { ex.printStackTrace(); return ""; } }
@RestControllerAdvice 与 @ControllerAdvice的区别:
@ControllerAdvice的类可以拥有@ExceptionHandler, @InitBinder或 @ModelAttribute注解的方法,并且这些方法会被应用到控制器类层次的所有@RequestMapping方法上
@RestControllerAdvice 类似于 @RestController 与 @Controller的区别
6.Thymeleaf与shiro标签整合使用
6.1前述
shiro自带了一些页面标签,可以使得前端页面结合后台进行一些权限控制
本次使用了thymeleaf与shiro标签的整合
6.2导入jar
<!--thymeleaf对shiro标签扩展-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
6.3在shiroconifg内配置ShiroDialect
/**
* @Description: 配置ShiroDialect,用于thymeleaf与shiro标签的配合使用
**/
@Bean
public ShiroDialect getShiroDialectt(){
return new ShiroDialect();
}
6.4在页面内使用shiro标签
<h3>(当前用户拥有相应权限,即可看到此功能)</h3>
<div shiro:hasPermission="user:add">
跳转用户添加<a href="add">添加</a>
</div>
<br/>
<div shiro:hasPermission="user:update">
跳转用户修改<a href="update">修改</a>
</div>
<br/>
<br/>
<a href="tologin">登录</a>
<br/>
可以利用shiro:hasPermission标签对资源权限进行验证
如上代码:拥有新增权限即可看到并操作新增功能,拥有修改权限即可看到并操作修改功能
三、总结
至此,shiro与springBoot的整合与使用(登录认证、资源权限认证)就完成了
总结了一下,Shiro配合SpringBoot的登录与权限认证,主要涉及三部分
1.在Controller/service接口入口处接收用户输入的账号密码,开始调用shiro的登录验证方法,并将账号密码传入shiro的token内
2.在UserRealm的doGetAuthenticationInfo方法的入参接收到账号密码信息,并进行相应登录认证处理
3.而ShiroConfig内,最主要是shiro内部的filter过滤器的配置,需要配置相应登录拦截路径,授权拦截路径,忽略路径,跳转登录路径...
Shiro基本功能
Shiro的核心架构API
(从外部看)
(从内部看)
核心词概念
subject 主体
principal 身份信息
credential 凭证信息
spring security spring安全框架
Authentication 身份认证/登录
Authorization 授权
Cryptography 加密
Concurrency 并发
realms 领域
authorzing 授权