Shiro:连接数据库实现认证、授权
2021-01-08 本文已影响0人
掌灬纹
Apache Shiro 一个简单的Java安全框架,在此框架基础上可以轻松实现网站的用户认证和授权
- Shiro的三大核心对象:
- Subject (代表当前用户对象)
- ShiroSecurityManager(管理所有Subject对象)
- Realm(管理数据:具体数据的授权与认证)
一、用户认证与拦截器
1.SpringBoot项目准备
- 导入案例涉及依赖
<dependencies>
<!--mybatis druid数据原-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--shiro spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--整合thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
- 配置Druid数据源,连接数据库
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
#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别名,mapper位置
mybatis:
type-aliases-package: com.ht.springbootshiro01.pojo
mapper-locations: classpath:mapper/*.xml
# 资源位置resources/mapper/*.xml
-
编写Dao、Service层 测试Mybatis正常使用
数据库表结构.jpg
(1)Dao(Mapper)层逻辑代码(省略实体类)
@Repository
@Mapper
// Dao 层
public interface UserMapper {
/**
* 根据用户名 查询指定用户
* @param name
* @return
*/
public User queryUserByName(@Param("name") String name);
}
(2)Mapper.xml文件编写(查询数据库)
<?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.ht.springbootshiro01.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select * from mybatis.user where name=#{name};
</select>
</mapper>
(3)Service层业务逻辑(调用Dao层)
@Service
public class UserService {
// service 调用 dao层
@Autowired
UserMapper userMapper;
public User queryUserByName(String name){
return userMapper.queryUserByName(name);
}
}
(4)测试代码(查询用户结果)
@SpringBootTest
class SpringbootShiro01ApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
// mybatis 测试
User admin = userService.queryUserByName("admin");
System.out.println(admin.toString());
}
}
至此查询成功后,SpringBoot 整合MyBatis数据库准备工作完成
2.基础网页路由配置
-
首页,登录页,管理员权限下的显示所有用户网页
页面分布.jpg - 跳转路由代码(Controller)
/**
* 管理路由跳转的Controller
*/
@Controller
public class RouteController {
@RequestMapping({"/index","/"})
public String index(Model model){
model.addAttribute("msg","Hello Shiro");
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
//user
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
// show
@RequestMapping("/show")
public String show(){
return "show";
}
}
3.编写Shiro配置文件,自定义Realm(管理授权、认证)
-
Shiro配置文件 配置三大要素:自定义Realm规则,SecurityManager安全管理对象,Shiro Filter拦截器相关信息配置
-
shiro的内置过滤器:
* anon: 无需认证就可访问 * authc: 认证后才可访问 * user: 必须拥有记住我功能才可访问 * perms:拥有对某个资源的权限才可访问 * role: 拥有某个角色的权限才可访问
@Configuration
public class ShiroConfig {
//1.创建Realm对象 自定义 注入bean
@Bean(name = "getUserRealm")
public UserRealm getUserRealm(){
return new UserRealm();
}
//2.SecurityManager Spring 注入参数 bean Qualifier(方法名)
@Bean(name = "getSecurityManager")
public DefaultWebSecurityManager defaultSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 Realm
securityManager.setRealm(userRealm);
return securityManager;
}
//3.Shiro Filter
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("getSecurityManager") DefaultSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置 安全管理
bean.setSecurityManager(securityManager);
//过滤器配置
Map<String, String> filterMap = new LinkedHashMap<>();
// /user 请求 拦截 认证后可访问
filterMap.put("/user/**","authc");
// 开放登录接口 首页
filterMap.put("/toLogin","anon");
filterMap.put("/index","anon");
filterMap.put("/","anon");
// 关闭其它路由 需授权访问
//filterMap.put("/**","authc");
bean.setFilterChainDefinitionMap(filterMap);
//System.out.println("Shiro拦截器注入成功");
// 自定义登录页面路径 实现登录拦截
bean.setLoginUrl("/toLogin");
return bean;
}
}
至此实现了Shiro的初步登录拦截,发现user/下的路由都不能访问
4.编写自定义Realm规则,认证 继承AuthorizingRealm
~ token令牌实际应用规则:获取前端登录传递的用户名、密码信息封装令牌,Shiro框架会自动经过自定义Realm 认证规则检验,与数据库中用户信息匹配比较
// 自定义UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了 授权 ==》" + principals.getRealmNames().toString());
return null;
}
// token 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了 认证 ==》" + token.getPrincipal().toString());
// 数据库取数据 进行认证
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 前端传递 用户名 查询 数据库
User user = userService.queryUserByName(userToken.getUsername());
if (null == user){
// 前端传递 与 数据库查询用户名 不一致
return null; // 抛出异常 未知用户名
}
// 默认密码不加密(不安全) 可以做 md5加密 或 md5盐值加密
// 密码认证 shiro做
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
- 登录请求处理:获取前端表单传递数据,封装令牌==》执行登录操作==》处理登录异常
@Controller
public class LoginController {
/**
* 登录请求 获取表单传递数据 封装令牌 token
* @param username
* @param password
* @param model
* @return
*/
@PostMapping("/login.do")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
System.out.println("登录信息:username: " + username + " ; password: " + password);
// 获取当前用户
Subject user = SecurityUtils.getSubject();
// 封装令牌 用于 Realm验证
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// 执行登录
try {
user.login(token); // 调用自定义 UserRealm 认证方法
return "index";
} catch (UnknownAccountException uae) {
// 用户不存在
System.out.println("There is no user with username of " + token.getPrincipal());
model.addAttribute("msg","用户名不存在");
return "login";
} catch (IncorrectCredentialsException ice) {
// 密码错误
System.out.println("Password for account " + token.getPrincipal() + " was incorrect!");
model.addAttribute("msg","密码错误");
return "login";
} catch (LockedAccountException lae) {
// 当前用户锁定
System.out.println("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
model.addAttribute("msg","用户锁定");
return "login";
} catch (AuthenticationException ae) {
//unexpected condition? error?
System.out.println("其它错误");
model.addAttribute("msg","其它错误");
return "login";
}
}
}
~ 测试Shiro的用户认证
测试认证.jpg
二、用户授权与前端整合
1.FilterMap中添加指定路由权限
Map<String, String> filterMap = new LinkedHashMap<>();
// 管理员开放权限
//filterMap.put("/user/**","perms[admin]");
// 授权 只有 拥有 user:add的权限才可访问 失败401 未授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
// /user 请求 拦截 认证后可访问
filterMap.put("/user/**","authc");
// 开放登录接口 首页
filterMap.put("/toLogin","anon");
filterMap.put("/index","anon");
filterMap.put("/","anon");
// 关闭其它路由 需授权访问
//filterMap.put("/**","authc");
bean.setFilterChainDefinitionMap(filterMap);
//System.out.println("Shiro拦截器注入成功");
// 自定义登录页面路径 实现登录拦截
bean.setLoginUrl("/toLogin");
// 设置未授权请求
bean.setUnauthorizedUrl("/unauthorized");
2.编写定制未授权跳转页面及路由控制器
/***
* 未授权控制器
*/
@Controller
public class UnauthorizedController {
/**
* 未授权页面
* @return
*/
@RequestMapping("/unauthorized")
public String toUnauthorized(){
return "error/unauthor";
}
}
*自定义未授权页面 /error/unauthor.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>未授权</title>
</head>
<body>
<div>
<h3 style="color: darkslategrey">未授权,请
<a th:href="@{/toLogin}">切换用户</a>,或进行授权操作</h3>
</div>
</body>
</html>
3.根据数据库中用户权限 为用户授权
- 自定义Realm 认证 传递给授权 携带(Principal == 令牌负责人即登录用户)
- 授权方法 取出负责人用户,根据数据库中用户权限授权
// 自定义UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了 授权 ==》" + principals.getRealmNames().toString());
// 增加授权 所有用户都赋予权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User principal = (User) subject.getPrincipal();
// 通过数据库字段 增加 权限
info.addStringPermission(principal.getPerms());
return info;
}
// token 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了 认证 ==》" + token.getPrincipal().toString());
// 数据库取数据 进行认证
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 前端传递 用户名 查询 数据库
User user = userService.queryUserByName(userToken.getUsername());
if (null == user){
// 前端传递 与 数据库查询用户名 不一致
return null; // 抛出异常 未知用户名
}
// 密码认证 shiro做 登录成功后将用户信息 传递给授权
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
4.增加前端显示用户信息 与 注销功能
- 首页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div>
<h1>首页</h1>
<!---->
<p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<!--用户信息-->
<p th:text="${user_name}"
th:if="${not #strings.isEmpty(user_name)}"></p>
</div>
<div>
<a th:href="@{/user/add}">增加</a>
<a th:href="@{/user/update}">修改</a>
<a th:href="@{/show}">展示</a>
<a th:href="@{/loginout.do}">注销</a>
</div>
</body>
</html>
- 注销控制器
@RequestMapping("/loginout.do")
public String loginOut(){
Subject subject = SecurityUtils.getSubject();
// 注销
subject.logout();
return "login";
}
- 登录成功 封装用户名
user.login(token); // 调用自定义 UserRealm 认证方法
model.addAttribute("user_name",token.getUsername());
return "index";
三、拓展整合Thymeleaf引擎
- 实现不同权限的用户显示不同操作
- 实现未登录用户 显示 登录按钮;已登录显示注销按钮
重要标签:
<!--头引用-->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
shiro:hasPermission="xxx" 拥有某权限
shiro:guest="true" 未登录
shiro:authenticated="true" 已登陆
- 完整首页代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div>
<h1>首页</h1>
<!---->
<p th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<!--用户信息-->
<p th:text="${user_name}"
th:if="${not #strings.isEmpty(user_name)}"></p>
</div>
<div>
<!--整合 shiro 根据权限显示-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">增加</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">修改</a>
</div>
<a th:href="@{/show}">展示</a>
<!--游客显示登录按钮 登录成功不显示-->
<div shiro:guest="true">
<a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:authenticated>
<a th:href="@{/loginout.do}">注销</a>
</div>
</div>
</body>
</html>
~体验到Shiro框架的便捷之处,告别过滤器