Spring Boot中集成Shiro
1.shiro简介
Apache Shiro是一个功能强大且灵活的开源安全框架,可以干净地处理身份验证,授权,企业会话管理和加密。
2.shiro功能介绍
Apache Shiro是具有许多功能的全面的应用程序安全框架。下图显示了Shiro核心功能
Shiro以Shiro开发团队所谓的“应用程序安全性的四个基石”为目标-身份验证,授权,会话管理和密码术:
- **身份验证:**有时称为“登录”,这是证明用户就是他们所说的身份的行为。
- **授权:**访问控制的过程,即确定“谁”有权访问“什么”。
- **会话管理:**即使在非Web或EJB应用程序中,也可以管理用户特定的会话。
- **密码术:**使用密码算法保持数据安全,同时仍然易于使用。
3.shiro核心组件
-
Subject(
org.apache.shiro.subject.Subject
)
当前与软件交互的实体(用户,第三方服务,计划任务等)的特定于安全性的“视图”。(把操作交给SecurityManager) -
SecurityManager(org.apache.shiro.mgt.SecurityManager)
安全管理器(关联Realm) - Realm(org.apache.shiro.realm.Realm)
领域充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。当真正需要与安全性相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个Realms中查找许多此类内容。您可以根据Realms
需要配置任意数量(通常每个数据源一个),并且Shiro会根据需要进行协调,以进行身份验证和授权。
4.shiro实战
4.1 环境构建
1.maven依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.自定义Realm
自定义 realm 需要继承 AuthorizingRealm 类,因 为该类封装了很多方法,它也是一步步继承自 Realm 类的,继承了 AuthorizingRealm 类后,需要重写 两个方法:
doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息
doGetAuthorizationInfo() 方法:用来为当前登陆成功的用户授予权限和角色
public class MyRealm extends AuthorizingRealm {
@Resource
UserService userService;
@Override
//用来为当前登陆成功的用户授予权限和角色
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
//用来验证当前登录的用户,获取认证信息
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
后续实现具体功能再对这两个方法进行具体实现和讲解;
3.shiro配置文件
1.ShiroFileterFactoryBean : 可以进行权限设置,访问控制等功能,由DefaultWebSecurityManager 对设置进行实现;
2.DefaultWebSecurityManager :根据自定义的Realm进行安全管理;
3.自定义Realm: 自定义验证规则;
@Configuration
public class ShiroConfig {
/*
1.ShiroFileterFactoryBean
* */
@Bean
public ShiroFilterFactoryBean getshShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//添加shiro内置过滤器
/*
* 常用过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user: 如果使用remmemberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* roles:该资源必须得到角色权限才可以访问
* */
//配置过滤规则
Map<String,String> filter=new LinkedHashMap<>();
filter.put("/user/add","anon");
filter.put("/user/update","authc");
//放行hello.html
filter.put("/hello.html","anon");
//拦截所有请求包括html也会被拦截
filter.put("/*","authc");
//将过滤规则设置到shiroFilterFactoryBean
shiroFilterFactoryBean.setFilterChainDefinitionMap(filter);
//访问被拦截后访问的地址,如/user/update被拦截时就会访问/user/toLogin;
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/*
* 2.DefaultWebSecurityManager
* */
@Bean(name = "securityManager")
public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("MyRealm")Realm myRealm) {
DefaultSecurityManager securityManager = new DefaultWebSecurityManager(myRealm);
return securityManager;
}
/*
* 3.自定义Realm
* */
@Bean(name = "MyRealm")
public Realm getRealm() {
return new MyRealm();
}
}
配置的过滤规则只有访问controller层进行跳转时才能生效,直接访问xxx.html页面是不生效的,也就是说即使你配置了拦截/add,也可以访问add.html;
4.2 登入验证
1.创建数据库表
暂时只用到用户表,角色表和权限表用于后续授权;
#角色表
CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `rolename` varchar(20) DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
#用户表
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键', `username` varchar(20) NOT NULL COMMENT '用户名', `password` varchar(20) NOT NULL COMMENT '密码' ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
#权限表
CREATE TABLE `t_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `permissionname` varchar(50) NOT NULL COMMENT '权限名', `role_id` int(11) DEFAULT NULL COMMENT '外键关联role', PRIMARY KEY (`id`), KEY `role_id` (`role_id`), CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
#用户与权限表
CREATE TABLE `user_role` (
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.自定义Realm的doGetAuthenticationInfo方法添加具体实现
public class MyRealm extends AuthorizingRealm {
@Resource
UserService userService;
@Override
//用来为当前登陆成功的用户授予权限和角色
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
//用来验证当前登录的用户,获取认证信息
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据token获取用户名,此Token是controller层穿来的,具体看下一步
String userName = (String) authenticationToken.getPrincipal();
// 根据用户名从数据库中查询该用户
User user = userService.getByUsername(userName);
if (user != null) {
//返回SimpleAuthenticationInfo来验证密码,只有user.getPassword()为必填参数,其他的可以填“”; //验证失败 subject.login(token);方法会出现IncorrectCredentialsException异常
return SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "MyRealm");
} else {
//返回null subject.login(token)方法会报UnknownAccountException异常;
return null;
}
}
}
3.编写controller
subject.login(token)这一步会带着存有前端传来的用户信息去自定义的Realm中的doGetAuthenticationInfo验证用户信息;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(User user, Model model) {
if (user == null) {
return "user/login";
} else {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword());
try {
subject.login(token);
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户不存在");
return "user/login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg","密码错误");
return "user/login";
}
}
return "success";
}
}
4.前端代码login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p style="color: red" th:text="${msg}"></p>
<form action="/user/login" >
<p>请输入userName:</p>
<input type="text" name="userName">
<p>请输入password:</p>
<input type="text" name="password" >
<input type="submit" value="submit">
</form>
</body>
</html>
5.测试
输入不存在用户时:
输入错误密码时:
输入正确密码:
登录验证成功后 shiroConfig中getshShiroFilterFactoryBean方法所配置的需要验证才能访问的请求(authc)就都能访问了;
4.3 添加角色和添加授权
数据库表结构:
t_user:
“id” “username” “password”
“1” “yg1” “a”
“2” “yg2” “a”
“3” “yg3” “a”
t_role
“id” “rolename”
“1” “admin”
“2” “boss”
“3” “user”
t_permission
“id” “permissionname” “role_id”
“1” “user:*” “1”
“2” “user:add” “2”
“3” “user:update” “3”
user_role
“u_id” “r_id”
“1” “1”
“2” “2”
“3” “3”
1.userMapper接口的编写
public interface UserMapper {
public Set<Role> getRoles(String userName);
public Set<Permission> getPermissions(String userName);
public User getByUsername(String userName);
}
2.userMapper.xml的编写
<!-- public User getByUsername(String userName);-->
<select id="getByUsername" resultType="User">
select * from t_user where username=#{userName}
</select>
<!-- public Set<Role> getRoles(String userName);-->
<select id="getRoles" resultType="Role">
SELECT tr.* from t_role tr,user_role ur where
tr.id=ur.r_id and ur.u_id in (select id from
t_user where username=#{userName})
</select>
<!-- public Set<Permission> getPermissions(String userName);-->
<select id="getPermissions" resultType="Permission">
SELECT tp.* from t_permission tp,user_role ur
where tp.role_id=ur.r_id AND ur.u_id in (select id
from t_user where username=#{userName})
</select>
3.userService编写
@Service
public class UserService {
@Resource
UserMapper userMapper;
public Set<Role> getRoles(String userName) {
if (userName == "" || userName == null) {
return null;
}
Set<Role> roleList = userMapper.getRoles(userName);
return roleList;
}
public Set<Permission>getPermissions(String userName) {
if (userName == "" || userName == null) {
return null;
}
Set<Permission> permissions = userMapper.getPermissions(userName);
return permissions;
}
public User getByUsername(String userName) {
if (userName == "" || userName == null) {
return null;
}
return userMapper.getByUsername(userName);
}
}
4.自定义Realm
AuthorizationInfo方法中添加角色和添加授权;
@Override
//用来为当前登陆成功的用户授予权限和角色
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权操作");
User user = (User) principalCollection.getPrimaryPrincipal();
String userName=user.getUserName();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//添加角色
Set<Role> roles = userService.getRoles(userName);
//将roles转为set<String>roleNames
Set<String>roleNames=new HashSet<>();
for (Role role : roles) {
roleNames.add(role.getRoleName());
}
authorizationInfo.setRoles(roleNames);
//添加授权
Set<Permission> permissionSet = userService.getPermissions(userName);
//将roles转为set<String>roleNames
Set<String>permissions=new HashSet<>();
for (Permission permission : permissionSet) {
permissions.add(permission.getPermissionName());
}
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
5.shiroConfig配置文件
在shiroConfig的ShiroFilterFactoryBean方法中添加角色认证和权限认证;
filter.put("/user/add",“perms[user:add]”);
filter.put("/user/update",“roles[admin,boss]”);
@Bean
public ShiroFilterFactoryBean getshShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro内置过滤器
/*
* 常用过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user: 如果使用remmemberMe的功能可以直接访问
* perms: 该资源必须得到资源权限才可以访问
* roles:该资源必须得到角色权限才可以访问
* */
Map<String,String> filter=new LinkedHashMap<>();
//filter.put("/user/add","anon");
//filter.put("/user/update","authc");
//授权过滤器
filter.put("/user/add","perms[user:add]");
filter.put("/user/update","roles[user,boss]");
//未授权就跳转到该页面
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unAuth");
//拦截所有请求包括html也会被拦截
// filter.put("/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filter);
shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
测试:
yg1有user:权限,yg2中有user:add权限所以可以访问*/user/add**,yg3没有这些权限不能访问,yg1没有被授权user或boss角色所以不能访问/user/update而yg2被授权boss,yg3被授权user所以能访问;
5 thymeleaf整合shiro
1.导入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.shiroConfig配置类添加bean
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
3.编写前端代码
xmlns:shiro=“http://www.pollix.at/thymeleaf/shiro” 导入后才能有shiro标签提示;
**xmlns:th=“http://www.thymeleaf.org” ** 导入后才能有thymeleaf标签提示;
shirol:标签后面的条件成立才会显示该标签的内容
常用shirol:标签解释
shiro:guest="" 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。
<p shiro:guest="">Please <a href="login.html">login</a></p>
shiro:user="" 认证通过或已记住的用户
<p shiro:user="">Welcome back </p>
shiro:authenticated="" 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在
<a shiro:authenticated="" href="update.html">Update your information</a>
shiro:principal 输出当前用户信息,通常为登录帐号信息。
<p>Hello, <shiro:principal/>, how are you today?</p>
shiro:notAuthenticated="" 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
shiro:hasRole=“xxx” 验证当前用户是否属于xxx角色
<a shiro:hasRole="admin" href="admin.html">Administer the system</a><!-- 拥有该角色 -->
shiro:lacksRole=“xxx” 与hasRole标签逻辑相反,当用户不属于该角色时验证通过
shiro:hasAllRoles=“xx1, xx2” 验证当前用户是否属于以下所有角色
shiro:hasAnyRoles="xx1, xx2 " 验证当前用户是否属于以下任意一个角色
shiro:hasPermission ="" 与Role的使用相同也有对应的lacksPermission等;
<!DOCTYPE html>
<html lang="en"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<div shiro:hasPermission="user:update">
<a href="/user/update">更新:</a>
</div>
<div shiro:hasPermission="user:add">
<a href="add">添加:</a>
</div>
</body>
</html>
测试结果:
*yg1有user:权限所以都能显示,yg2只有user:add权限只能显示添加,yg2只有user:update权限只能显示更新;
只有经过controller层进行模板解析后跳转的html页面shiro标签才会起效;
上一篇: String文件对文案部分加粗变色处理
下一篇: win10玩游戏掉帧严重怎么处理
推荐阅读
-
使用Spring Boot集成FastDFS的示例代码
-
Spring Boot与Spark、Cassandra系统集成开发示例
-
spring boot 2 集成JWT实现api接口认证
-
Spring Boot 入门(五):集成 AOP 进行日志管理
-
spring boot 中设置默认网页的方法
-
Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制
-
浅谈Spring Boot中Redis缓存还能这么用
-
spring boot devtools在Idea中实现热部署方法
-
SpringBoot 源码解析 (十)----- Spring Boot 精髓:集成AOP
-
SpringBoot系列:Spring Boot集成定时任务Quartz