SpringBoot:整合Shiro
什么是Apache Shiro?
Apache Shiro是一个功能强大且易于使用的Java安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话管理解决方案。
Shiro官网上有一个十分钟快速入门的教程:关于Apache Shiro的10分钟教程,并且从官网上下载Shiro以后,源码里有/samples/quickstart下也有一个Quickstart类,用于快速上手Shiro。
十分钟教程和Quickstart类都是Shiro给我们的上手指南,他的目的是让我们熟悉API和Shiro的概念。
Quickstart类通过机翻和自己的辣鸡翻译加上的注释
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// 在类路径的根目录下使用shiro.ini文件,返回一个SecurityManager实例:
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//得到 securityManager 实例
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前执行的用户
Subject currentUser = SecurityUtils.getSubject();
// 使用会话做一些事情(不需要web或EJB容器!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 让我们登录当前用户,这样我们可以检查角色和权限:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// 在这里捕获更多的异常(可能是特定于您的应用程序的自定义异常)
catch (AuthenticationException ae) {
//以防一些为止的错误和意外
}
}
//说出他们是谁:
//打印它们的标识主体(在本例中为用户名):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
// 判断当前用户是否是schwartz
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
// 判断当前用户是否有权做些什么(细粒度)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//判断当前用户是否有权做些什么(细粒度,更详细的权限检查)
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//注销
currentUser.logout();
System.exit(0);
}
}
总结出来Shiro的几个核心API
SecurityManager securityManager = factory.getInstance(); // 得到SecurityManager实例
Subject currentUser = SecurityUtils.getSubject(); // 获取当前执行的用户
currentUser.isAuthenticated() // 判断用户是否已登录
currentUser.hasRole("schwartz") // 判断用户的角色
currentUser.isPermitted("lightsaber:wield") // 判断用户是否有权做什么
currentUser.logout(); // 注销
Shiro架构(从外部来看)
一个最简单的Shiro应用,应用代码通过Subject来进行认证和授权,Subject会托付给SecurityManager来执行,我们需要给SecurityManager注入Realm,让SecurityManager能得到合法的用户及其权限进行判断。
Shiro架构(从内部来看):
- Subject:是任何可以与应用交互的用户
- SecuurityManager:相当于SpringMVC中的DispatcherServlet,是Shiro的心脏,所有的具体交互都通过SecurityManager进行控制,它管理着所有的Subject,且负责进行认证,授权,会话及缓存管理
- Authenticator:负责Subject认证,是一个拓展点,可以自定义实现;可以使用认证策略(Authercication Strategy),即什么情况下算认证通过
- Authorizer:授权器,即访问控制器,用来决定主体是否有权进行相应的操作,即控制着用户能访问应用中的哪些功能;
- Realm:可以有一个或者多个Realm,可以认为是安全实体的数据源,即用于获取安全实体,可以是JDBC实现,也可以是内存实现等,由用户提供。
- SessionManager:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSe环境
- CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能
- Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。
Shiro的核心概念:Subject, SecuurityManager, Realms。
整合Shiro之前的准备工作
SpringBoot的环境前提:导入了Spring-web和Thymeleaf的依赖
准备工作:
- HTML:编写首页,有一个add的跳转和一个update的跳转 ,对应的add页面和update页面。
- Java:编写一个用于跳转的Controller
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<a th:href="@{'/user/add'}">Add</a>
<a th:href="@{'/user/update'}">Update</a>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Add</h1>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Update</h1>
</body>
</html>
Controller:
/**
* @author Claw
*/
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String test(Model model){
model.addAttribute("msg","hello,Shiro");
return "index";
}
@RequestMapping("/user/add")
public String toAdd(){
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate(){
return "user/update";
}
}
效果:点击Add和Update分别能进入Add和Update的页面
SpringBoot整合Shiro环境搭建
Shiro实现登录拦截
但是总所周知,既然是学安全的框架,这两个操作不是谁都可以的。
那么我们现在需要使Shiro对操作进行拦截,允许有权限的人进行操作,那么再执行了安全保护后,再点击Add和Update应该指向登录页面。
那么在MyController添加一个方法对登录页面的跳转,以及再编写一个login.html
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="@{'/login'}">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password">
<input type="submit" value="提交">
</form>
<span th:text="${msg}" style="color: red"></span>
</body>
</html>
导入Spring整合Shiro的依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.2</version>
</dependency>
SpringBoot集成Shiro只需要两个类 ,Shiro的配置类ShiroConfig以及自定义的Realm类 UserRealm。
ShiroConfig是对Shiro的一些配置,比如过滤的文件和权限,密码加密的算法等相关功能。
UserRealm 是自定义的Realm类,继承了AuthorizingRealm,并且重写了doGetAuthorizationInfo(权限相关)和doGetAuthenticationInfo (身份认证)两个方法。
ShiroConfig方法的编写需要三个方法,再写这三个方法前,必须再强调Shiro的三个核心概念:
- Subject:代表当前正在执行操作的用户,只要对应用进行了操作,那么就可以认为他是那个Subject
- SecurityManager:SecurityManager是Shiro的核心,主要协调Shiro内部的各种安全组件,它需要注入Realm,让SecurityManager可以拿到合法的用户对其权限的判断。
- Realm:用户数据和Shiro数据交互的桥梁,比如需要用户认证,权限认证,都从Realm来读取数据。
ShiroConfig的三个方法,主要用来得到自定义的Realm对象,把得到的Realm对象注入给SecurityManager对象,再把SecurityManager对象给ShiroFilter。
ShiroFilter :是Shiro的过滤器,可以设置登录页面,权限不足跳转页面,具体某些页面的权限控制和身份认证。
它需要注入securityManager,我们通过ShiroFilterFactoryBean来得到Shiro的内置Filter。
我的建议从下往上来编写ShiroConfig,即:
获取Realm对象===>获取securityManager对象===》把Realm对象注入给securityManager对象===》把securityManager对象注入给ShiroFilter
为了得到Realm对象,首先来自定义UserRealm,继承AuthorizingRealm类并且重写父类的方法。
/**
* 自定义的UserRealm
* 继承AuthorizingRealm
* 并重写 doGetAuthorizationInfo 和 doGetAuthenticationInfo
* @author Claw
*/
public class UserRealm extends AuthorizingRealm {
/**
* 权限相关
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("获取授权信息");
return null;
}
/**
* 身份认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("获取认证信息");
return null;
}
}
我们定义好自定义的UserRealm后,在ShiroConfig中new一个就得到了。
那么如何得到SecuiryManager呢?SecuiryManager本身是一个接口,要得到它要看实现类,因为项目是Web工程,所以使用的是DefaultWebSecurityManager这个实现类,new一个DefaultWebSecurityManager得到它的对象,然后注入realm。
那么ShiroFilter如何得到呢?通过ShiroFilterFactoryBean来得到Shiro的内置Filter,它的源码里说了它需要一个map,map里面的Key来存放需要被保护的URL,value是 内置过滤器的名字
常用的内置Filter:
anon 无需认证即可访问
authc 必须认证了才能访问
user 必须拥有记住我 功能才能用
perms 拥有对某个资源的权限
三个方法 1.得到Realm 2.得到Security,注入Realm 3.得到Security,注入给Fliter。
我们已经很明确思路了,现在来编写ShiroConfig,不要忘记@Configuation注解表明是配置类,也不要忘记给每个获取的对象的方法打上@Bean注解,我们需要让Spring容器来管理它们。
/**
* @author Claw
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean bean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//注入SecurityManager
bean.setSecurityManager(securityManager);
//设置Fliter 需要一个map
Map<String, String> filterMap= new HashMap();
//Key 来定义哪些URL需要被限制,value是内置Flter过滤器名称,authc:必须认证了才能访问
filterMap.put("/user/**","authc");
bean.setFilterChainDefinitionMap(filterMap);
//没有得到认证的请求会跳转到登录页进行授权
bean.setLoginUrl("/toLogin");
return bean;
}
/**
* 获取 ecurityManager
* @param userRealm
* @return securityManager
*/
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(userRealm);
return securityManager;
}
/**
* 获取 Realm对象
* @return userRealm
*/
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
到了这一步,我们已经对访问add页面和update的页面进行了权限的拦截,未授权的访问将会转到登录页面。
Shiro实现用户认证
身份验证的三个流程:
1、收集用户身份/凭证,即如用户名/密码
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
3、创建自定义的 Realm 类,继承AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法
针对于第一点,username和password从登录的表单中获取,针对于第二点,在Login方法中调用Subject.login进行登录,针对第三点,我们已经自定义了UserRealm,认证的条件从realm中获取,因为目前还没有继承Mybatis,我们暂时可以把username和passowrd写死在Realm。
现在来编写处理用户登录的Login方法,在Shiro官方给出的快速上手的例子中,已经说明了如何编写,我们仿照写就ok。
// 如果用户被认证了
if (!currentUser.isAuthenticated()) {
// 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 记住我功能
token.setRememberMe(true);
try {
//调用登录方法
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// 在这里捕获更多的异常
catch (AuthenticationException ae) {
//以防一些为止的错误和意外
}
}
Login方法
/**
* @author Claw
*/
@Controller
public class LoginController {
@RequestMapping("/login")
public String Login(String username,String password,Model model){
//封装当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//执行登录方法
try{
subject.login(token);
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
return "index";
}
}
写完这个以后我们再去完善UserRealm的身份认证方法 doGetAuthenticationInfo(AuthenticationToken authenticationToken)
当我们得到表单提交过来的用户名和密码,将它封装到UsernamePasswordToken里,然后调用login方法,SecurityManager会收到AuthenticationToken的实例,把它发送到已定义的Realm里,执行必要的认证和检查。
在Login的方法中我们用了UsernamePasswordToken来封装用户名和密码,所以在Realm中,需要把参数AuthenticationToken 强转为UsernamePasswordToken,这是一个固定的写法。token定义好以后是全局都可以使用的。
用户名认证通过我们自己判断,密码是交给Shiro来做。
doGetAuthenticationInfo方法是要求返回一个AuthenticationInfo类型的对象,方法里直接new一个return回去就好,但是AuthenticationInfo是个接口,我们要返回它的实现类,这里用到的是SimpleAuthenticationInfo,它有三个参数,第一个参数是获取用户的认证,第二个是它要传递的密码,第三个是认证名。认证和认证名现在可以省略掉,我们需要把正确的密码传进去。
UserRealm:
/**
* 自定义的UserRealm
* 继承AuthorizingRealm
* 并重写 doGetAuthorizationInfo 和 doGetAuthenticationInfo
* @author Claw
*/
public class UserRealm extends AuthorizingRealm {
/**
* 权限相关
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("获取授权信息");
return null;
}
/**
* 身份认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("获取认证信息");
/**
* 定义正确的用户名和密码,一般从数据库取,这里写死了.
*/
String username = "admin";
String password = "12345";
//我们用UsernamePasswordToken来封装的数据,把authenticationToken强转为UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//验证用户名,如果不正确,返回null,会在Login方法中捕获UnknownAccountException的异常
if(!token.getUsername().equals(username)){
return null;
}
//密码验证交给Shiro来验证
return new SimpleAuthenticationInfo("",password,"");
}
}
写到这一步,我们已经完成了Shiro实现用户认证了。
登录成功的测试:
Shiro整合Mybatis
其实也就是SpringBoot整合mybatis,这里我同时用了durid的数据源。
步骤:
- 导入依赖
- 创建POJO实体类
- 创建Mapper以及配置映射文件
- 在配置文件中为mybatis做配置
- 创建Service接口以及实现类
导入依赖:
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
POJO:
/**
* @author Claw
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private String pwd;
}
Mapper :
@Repository
@Mapper
public interface UserMapper {
/**
* 通过用户名查找用户
* @param name
* @return
*/
User findUserByName(String name);
}
配置映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.claw.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from
user
</select>
<select id="findUserByName" resultType="user" parameterType="String">
select * from user where username=#{name}
</select>
</mapper>
配置文件 yaml格式:
#thymeleaf缓存关闭 便于调试
spring:
thymeleaf:
cache: false
#配置数据库源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf-8
username: root
password: password
type: com.alibaba.druid.pool.DruidDataSource
#druid 数据源私有属性
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#mybatis的配置
mybatis:
mapper-locations: com/claw/mapper/*.xml
type-aliases-package: com.claw.pojo
Service接口:
/**
* @author Claw
*/
public interface UserService {
/**
* 通过用户名查找用户
* @param name
* @return
*/
User findUserByName(String name);
}
Service实现类:
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User findUserByName(String name) {
return userMapper.findUserByName(name);
}
}
接下来在Realm中通过数据库获取数据,再进行用户名和密码的判断。
我的数据库本来没有密码这一列,我添加了一列,并且新插入了个admin的用户
/**
* 身份认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("获取认证信息");
//我们用UsernamePasswordToken来封装的数据,把authenticationToken强转为UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//从数据库取用户
User user = userService.findUserByName(token.getUsername());
//如果用户名为空 返回null,会在Login方法中捕获UnknownAccountException的异常
if (null == user){
return null;
}
//密码验证交给Shiro来验证
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
Shiro请求授权
在Realm中只完成了认证,让登录的用户可以访问某些页面,但还未进行授权,也就是说登录的用户,当然也不能随便的进行操作。
所以还要完成授权,这一步需要在ShiroConfig中定义,在设置内置过滤器的地方添加一个 perms的过滤器:让/user/add这个访问路径需要有user:add的权限才可以进入。
//授权: 授权才能操作
filterMap.put("/user/add","perms[user:add");
当然我们现在未定义user:add,现在重启SpringBoot去访问Add页面,会显示未授权。
这个错误的页面我们可以自己定义,当未授权的访问的时候跳转至未授权页面。
定义一个空白html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
</h1>
</body>
</html>
定义一个方法跳转视图
@RequestMapping("/unaut")
@ResponseBody
public String unauthorized() {
return "您没有权限访问此页面";
}
在ShiroConfig中指定这个页面
@Bean
public ShiroFilterFactoryBean bean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//注入SecurityManager
bean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
Map<String, String> filterMap= new LinkedHashMap<>();
//授权: 授权才能操作 只允许带user:add这个字符串的人才能访问add页面
filterMap.put("/user/add","perms[user:add");
//authc:认证才能访问
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//没有得到认证的请求会跳转到登录页进行授权
bean.setLoginUrl("/toLogin");
//没有得到授权会跳转至未授权页面
bean.setUnauthorizedUrl("/unaut");
return bean;
}
未授权的用户再次访问这个页面将被重定向到未经授权的页面。
目前我们只是让add页面会被有权限的人访问到,而这个权限我们还未定义,这里仅仅只是做了拦截而已。
定义权限要在UserRealm的 doGetAuthorizationInfo(PrincipalCollection principalCollection)方法中完成,授权方法和身份认证的单词长得实在太像了,通过参数名更好区分一点。
当我们登录的时候,会走到Realm的doGetAuthenticationInfo(身份认证方法),后台的打印日志也能看到。
获取认证信息
登录成功再去访问add页面的时候,会走到 doGetAuthorizationInfo(授权方法)后台的打印日志也能看到。
获取授权信息
所以,我们可以在 doGetAuthorizationInfo 中,让走进授权方法的用户,得到授权。
doGetAuthorizationInfo类会返回AuthorizationInfo类型的对象,AuthorizationInfo是个接口,我们会返回它的实现类。
这里用到了 SimpleAuthorizationInfo,这个对象有很多权限相关的方法,之前我们设置了 user:add 才能访问add
所以我们使用 addStringPermission()方法为进入授权方法的用户添加上 user:add 的权限
这样的话,登录的用户就能访问了。
/**
* 权限相关
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("获取授权信息");
//SimpleAuthorizationInfo 授予权限的方法 一个固定的套路
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
return info;
}
这里为了初步尝试授权而写死了授权规则,通常通过数据库来获取授权规则。角色不同,对应的权限不同,我们需要对user表进行修改,添加一列perms列,来存放权限相关的内容。
相对于的,POJO也要记得添加上新添加的列为属性。
我们从认证方法中得到的用户的信息,要如何在授权方法中用到他呢?
我们之前在认证方法中 return SimpleAuthenticationInfo对象,第一个参数是省略的,这个参数其实就是我们已经得到认证的用户。
把user扔进这个参数里,再从授权方法中获取。
这样,我们就拿到了认证用户的授权规则,进行有效的授权了。
Shiro整合Thymeleaf
现在我们需要做的事情是,让能进入add页面能看见add链接,也就是有相应权限的人能看到这个链接。
要完成这个需要Shiro与Thymeleaf的整合。
第一步,导入依赖
第二步,整合ShrioDialect
导入依赖
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
整合ShiroDialect ShiroDialect是thymeleaf-extras-shiro提供的类,我们只需要new一个retrun回去并打上@Bean标签就可以了。
/**
* ShiroDailect 用来整合Shiro Thymeleaf
* @return
*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
关于Thymeleaf为Shiro整合的标签,thymeleaf-extras-shiro的GitHub上很多例子提供。Github地址:thymeleaf-extras-shiro
总结下来如下: 参考: shiro权限验证标签
<!--The guest tag-->
<!--验证当前用户是否为“访客”,即未认证(包含未记住)的用户-->
<p shiro:guest="">
Please <a href="login.html">Login</a>
</p>
<!--The user tag-->
<!--认证通过或已记住的用户。-->
<p shiro:user="">
Welcome back John! Not John? Click <a href="login.html">here</a> to login.
</p>
<!--The authenticated tag-->
<!--已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。-->
<a shiro:authenticated="" href="updateAccount.html">Update your contact information</a>
<!--The notAuthenticated tag-->
<!--未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。 -->
<p shiro:notAuthenticated="">
Please <a href="login.html">login</a> in order to update your credit card information.
</p>
<!--The principal tag-->
<!--输出当前用户信息,通常为登录帐号信息。-->
<p>Hello, <span shiro:principal=""></span>, how are you today?</p>
or
<p>Hello, <shiro:principal/>, how are you today?</p>
Typed principal and principal property are also supported.
<!--The hasRole tag-->
<!--验证当前用户是否属于该角色。-->
<a shiro:hasRole="administrator" href="admin.html">Administer the system</a>
<!--The lacksRole tag-->
<p shiro:lacksRole="administrator">
Sorry, you are not allowed to administer the system.
</p>
<!--The hasAllRoles tag-->
<!--与hasRole标签逻辑相反,当用户不属于该角色时验证通过。-->
<p shiro:hasAllRoles="developer, project manager">
You are a developer and a project manager.
</p>
<!--The hasAnyRoles tag-->
<!--验证当前用户是否属于以下任意一个角色。 -->
<p shiro:hasAnyRoles="developer, project manager, administrator">
You are a developer, project manager, or administrator.
</p>
<!--The hasPermission tag-->
<!--验证当前用户是否拥有指定权限。-->
<a shiro:hasPermission="user:create" href="createUser.html">Create a new User</a>
<!--The lacksPermission tag-->
<!--与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。-->
<p shiro:lacksPermission="user:delete">
Sorry, you are not allowed to delete user accounts.
</p>
<!--The hasAllPermissions tag-->
<p shiro:hasAllPermissions="user:create, user:delete">
You can create and delete users.
</p>
<!--The hasAnyPermissions tag-->
<p shiro:hasAnyPermissions="user:create, user:delete">
You can create or delete users.
</p>
所以如果我们想让拥有add的权限的人看见add链接,那么使用hasPermission 的Tag就可以实现,判断它是否有 user:add的授权规则即可。
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro=" htttp://thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<div shiro:hasPermission="user:add">
<a th:href="@{'/user/add'}">Add</a>
</div>
<a th:href="@{'/user/update'}">Update</a>
</body>
</html>
测试:
未登陆前,只能看见update标签了。
使用admin登录(admin有add的权限)
可以试着更完善一点,未登录的时候需要看见登录链接,而登录以后就看不到登录链接了。 这里用到了是 <shiro:guest>标签。
<shiro:guest>
请 <a th:href="@{/toLogin}">登录</a>
</shiro:guest>
测试效果:
未登录
总结:
一个简单的Shiro与Thymeleaf的整合demo,我们能得到一个步骤思路:
- 导入Shrio整合Spring的依赖
- 编写ShroConfig
- 通过三个方法来获取自定义Realm,securityManager,ShiroFilterFactoryBean。
- 编写自定义Realm类
- 认证规则的编写
- 授权,验证用户名和密码
- Shrio与Thymeleaf的整合
- 让前端页面能通过一些标签判断是否有权限访问某些内容。
参考:
【狂神说Java】SpringBoot最新教程IDEA版通俗易懂