Shiro权限控制
0.1RBAC认证方式
Role Based Access Controller :基于角色的访问控制
0.2 RBAC认证方式下的数据库设计
1. 权限控制
1.1 概述
1.1.1什么是认证和授权
-
认证和授权,控制项目资源的访问。
- 认证:先进行认证。例如:是否是QQ会员-----在程序中指的就是:登录
- 授权:再进行授权。例如:QQ会员级别(级别不同权限不同)-------对菜单的访问控制
1.1.2 权限控制的解决方案
- 方式1:自定义实现
- 自己如何实现一个认证?写一个Filter过滤器
- 方式2:采用框架,例如:shiro、Spring Security 等
- Shiro:轻量、简单,apache
- Spring Security:轻量、简单,Spring
1.1.3 Shiro概述
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
官网:http://shiro.apache.org/
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
1.1.4 三个核心组件
1.1.5 传统的登录和shiro登录的比较
shiro的使用不依赖Spring框架,javase可以用,javaee也可以用,移动应用程序可以用,大型的网络和企业应用程序也可以使用Shiro。
传统登录方式:
Shiro安全框架实现登录
传统的方式,客户端发出请求给Controller,Controller接受用户的用户名和密码,然后由Controller调用业务逻辑,在Controller中调用Service,然后service负责调用数据库进行处理,如果用户名和密码正确,则将用户信息保存到session中,并且进入主页面
如果用户名和密码错误,则保存错误信息,然后将信息输出到客户端。
答:客户端发送请求到Controller,Controller接受用户的用户名和密码,第一步还是一样的,但是第二步不一样了,以前第二步是Controller直接调用Service处理,现在Controller调用Shiro安全框架去处理,也就是说将认证授权抽取出来,有一个框架专门为你做认证做授权,这里有框架去帮我们完成认证和授权,然后告诉你这个用户名和密码是否可用还是不可用,当然此时,认证成功之后,只要从shiro中取出认证的结果,如果成功的话,将用户保存至session中,然后在跳转页面
这个过程相比于早期的操作,相当于验证用户名和密码的业务逻辑交给shiro安全框架来做。并且加密也交给shiro安全框架来做,然后由shiro安全框架来加密,由shiro拿密文与数据库中的密文进行比较。
总结一下:shiro就是一个安全框架,帮助我们解决认证、授权、加密和密码比较的过程。
1.2 整合shiro
1.2.1 maven坐标
步骤1:在common-parent项目中,添加坐标
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro end-->
1.2.2 配置过滤器
使用Shiro时,需要配置的相关权限过滤器如下(共10个):
anon: 匿名过滤器,未登陆也可以访问
authc: 认证过滤器, 登陆后访问
perms : 需要xx权限,才能访问
roles: 需要xx角色,才能访问
user: 需要xx用户,才能访问
port:指定端口才能访问
ssl:必须使用https协议才能访问
logout :登出功能
rest :根据指定HTTP请求访问才能访问 ,get方式提交 或者 post方式提交才能访问
1.2.3 配置config类
shiro的配置步骤
1 配置安全管理器SecurityManager
2 realm域配置:由于SecurityManger需要使用realm域,涉及到用户信息、权限信息,处理用户信息的时候需要加密
3 密码比较器:用户输入的铭文进行加密,并且与数据库中的密文进行比较
4 配置生成过滤器的工厂类
/**
* 在ShiroConfig中做什么事情呢?
* 1 配置shiro安全管理器,向安全管理器中注入Realm域
* 2 配置Realm域:注入密码比较器
* 3 配置密码比较器
* 4 配置拦截路径和放行路径
*/
@Configuration
public class ShiroConfig {
/**
* 配置安全管理器,并且注入Realm域
* @param realm
* @return
*/
@Bean
public SecurityManager securityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
/**
* Credentials:凭证/证书 ---
*
* 配置Realm域,注入密码比较器
* @param credentialsMatcher
* @return
*/
@Bean
public BosRealm realm(CredentialsMatcher credentialsMatcher){
BosRealm bosRealm = new BosRealm();
bosRealm.setCredentialsMatcher(credentialsMatcher);
return bosRealm;
}
/**
* 密码比较器
*
* @return
*/
@Bean
public CredentialsMatcher credentialsMatcher(){
// return new HashedCredentialsMatcher("MD5");
return new BosCredentialsMatcher();
}
/**
* 配置拦截路径和放行路径
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
// shiro过滤器工厂类
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器----Map集合
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/login*", "anon");
filterChainDefinitionMap.put("/user/login*", "anon");
filterChainDefinitionMap.put("/validatecode.jsp*", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/data/**", "anon");
// /** 匹配所有的路径
// 通过Map集合组成了一个拦截器链 ,自顶向下过滤,一旦匹配,则不再执行下面的过滤
// 如果下面的定义与上面冲突,那按照了谁先定义谁说了算
// /** 一定要配置在最后
filterChainDefinitionMap.put("/**", "authc");
// 将拦截器链设置到shiro中
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
return shiroFilterFactoryBean;
}
/**
* 开启shiro aop注解支持
* 使用代理方式;所以需要开启代码支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 开启cglib代理
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
根据配置文件报错显示,需要创建如下文件:
1 Realm域
2 CredentialsMatcher密码比较器
1.2.4 创建类Realm类并实现认证和授权方法
//自定义Realm ,实现安全数据 连接
public class BosRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
@Override
public String getName() {
return "bosRealm";
}
@Override
// 认证...
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("shiro 认证管理... ");
//1 获得密码
String username = (String)token.getPrincipal();
//2 通过username从数据库中查找 User对象,如果找到,没找到.
User user = userService.findUserByUsername(username);;
if(user == null){
//返回null表示账号不存在
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
@Override
// 授权...
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("shiro 授权管理...");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据当前登录用户 查询对应角色和权限
// User user = (User) SecurityUtils.getSubject().getPrincipal();
User user = (User) pc.getPrimaryPrincipal();
// 调用业务层,查询角色
List<Role> roles = roleService.findByUser(user);
for (Role role : roles) {
authorizationInfo.addRole(role.getKeyword());
}
// 调用业务层,查询权限
List<Permission> permissions = permissionService.findByUser(user);
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getKeyword());
}
return authorizationInfo;
}
}
1.2.5 编写密码比较器
public class BosCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//向下转型
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
//获取用户页面输入的密码
String pwd = new String(upToken.getPassword());
//加密
String newPwd =Encrypt.md5(pwd, upToken.getUsername()).toString();
//获取数据库密码
String dbPwd = info.getCredentials().toString();
return equals(newPwd, dbPwd);
}
}
1.2.6编写加密工具类
public class Encrypt {
/*
* 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
* 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,
* 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
* 可以到一些md5解密网站很容易的通过散列值得到密码“admin”,
* 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
* 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
*/
//高强度加密算法,不可逆
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
public static void main(String[] args) {
/**
* new Md5Hash("123456","lisi",1) :1b539b60601b934441308049a9526e7d
* new Md5Hash("123456","lisi",2) :42bd4e7685cb11d3ba02716c313cb04b
* new Md5Hash("123456","lisi",3) :16f807d62105b4896034552ee5caeb8a
* new Md5Hash("123456","KMNO4",3):8bd35dc14dc07f756478bb44513694f6
*/
//System.out.println(new Md5Hash("123456","KMNO4",3).toString());
/**
* sha家族加密算法
* sha1:aca1eb31d2dcf8f1fcf3fd7a7104232785afad41 40 位
* sha256: 616a47d8e1e42f23693bb3a85749bf18d4b6e5380ddfd5717aafa61e33d5211e
* sha384:84f5cbb18e2d9f1c81b8cec6f443a2b229993689a2ebae97db37e13af1dfb00ec6168713a53fe19d33a63d4d30889553
* sha512:c3e5102b6a7ec6caa5b255dae2895b11c2ef0c7b9bfea8e848653372b53f3ef665d96ea283a21eac683cc0fe5c4b1f64692c2056a8a9636ee1931151043d2b5d
*/
System.out.println("sha1:"+new Sha1Hash("123456","lisi",2));
System.out.println("sha256:"+new Sha256Hash("123456","lisi",2));
System.out.println("sha384:"+new Sha384Hash("123456","lisi",2));
System.out.println("sha512:"+new Sha512Hash("123456","lisi",2));
}
}
1.2.7 确定权限不足时,显示未授权页面
- 确定页面
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/login*", "anon");
filterChainDefinitionMap.put("/validatecode.jsp*", "anon");
filterChainDefinitionMap.put("/user/login*", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/services/**", "anon");
filterChainDefinitionMap.put("/pages/base/courier**", "perms[courier:list]");
filterChainDefinitionMap.put("/pages/base/area**", "roles[base]");
//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized.html");
1.2.8 细粒度方法权限控制
需求:张三可以查看快递员列表信息,但是无法添加快递员
Shiro提供了若干注解用于在方法上进行权限控制
- @RequiresPermissions()
用户必须具有指定【权限】,才可以访问被注解修饰的方法。 - @RequiresRoles()
用户必须具有指定【角色】,才可以访问被注解修饰的方法。
- 步骤1:在授权方法中获取该用户的所有的权限:包括role权限和Permission权限
- 步骤2:在运单 CourierController的添加方法上添加courier:list权限标识。
测试:无权限访问
1.3动态菜单
动态菜单: 不同用户登录后,应该看到不同菜单结构
1、 修改index.html 加载基本菜单 url路径
// 基本功能菜单加载
$.get("/menu/showMenu",function(data){
$.fn.zTree.init($("#treeMenu"), setting, data);
},"json");
2、 在MenuController 添加 showMenu方法
@RestController
@RequestMapping("/menu")
public class MenuController {
@Autowired
private MenuService menuService;
// 加载左侧的菜单功能
@GetMapping(value = "/showMenu")
public ResponseEntity<List<Menu>> showMenu(){
// 调用业务层,查询当前用户具有菜单列表
Subject subject = SecurityUtils.getSubject();
User user = (User)subject.getPrincipal();
// 查询菜单列表
List<Menu> result = menuService.findByUser(user);
return new ResponseEntity<List<Menu>>(result,HttpStatus.OK);
}
}
3、 编写MenuService.java业务层
@Service
@Transactional
public class MenuService {
@Autowired
private MenuMapper menuMapper;
/**查询用户*/
public List<Menu> findByUser(User user) {
// 针对admin用户显示所有的菜单
if(user.getUsername().equals("admin")){
return menuMapper.selectAll();
}
else{
// 使用用户ID,查询当前用户具有的菜单列表
return menuMapper.findByUser(user.getId());
}
}
}
4、调用DAO
@org.apache.ibatis.annotations.Mapper
public interface MenuMapper extends Mapper<Menu> {
@Select("select m.* from t_menu m,t_user u,t_user_role ur,t_role r,t_role_menu rm "
+ "where m.id = rm.menu_id and rm.role_id = r.id "
+ "and r.id = ur.role_id and ur.user_id = u.id "
+ "and u.id=#{id} order by m.priority")
List<Menu> findByUser(Integer id);
}
狂神老师
上面已经忘了是什么时候总结的了,粗略的看了一下,总结的还挺详细,
狂神老师的已经全看完了,在这儿也记录一下,两份笔记总结着看吧
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- shiro thymeleaf 整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.1.47</version>-->
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--
Subject 用户
SecurityManager 管理所有用户
Realm 连接数据
-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.52.3:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# url: jdbc:mysql://192.168.66.3:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
# 指定数据源,默认的数据源是HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
# springboot 默认是不注入这些属性的,需要自己绑定
# Druid数据源专有配置(企业里一般也是直接copy的)
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 监控统计,log4j 日志记录,wall 防sql注入
# 如果报错,导入log4j依赖即可
filters: stat,wall,log4j
MaxPoolPreparedStatementPerConnectionSize: 20
setUseGlobalDataSourceStat: true
connectProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
# pojo别名扫描包
type-aliases-package: com.example.shiro.pojo
# 加载Mybatis映射文件
# Mapper接口和映射文件在同一目录下的话,就只用在启动类上添加@MapperScan,
# 不用写配置文件中的Mapper路径了
# mapper-locations: classpath:mapper/*Mapper.xml
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
[[${msg}]]
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<a th:href="@{/user/update}">update</a>
<a th:href="@{/toLogOut}">logOut</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="登录">
</form>
</body>
</html>
package com.example.shiro;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.example.shiro.dao")
public class SpringShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringShiroApplication.class, args);
}
}
package com.example.shiro.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms;
}
package com.example.shiro.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@GetMapping(value = {"index","/","/index.html"})
public String toIndex(Model model){
model.addAttribute("msg","hello,shiro");
return "index";
}
@GetMapping(value = {"user/add"})
public String add(Model model){
model.addAttribute("msg","add,shiro");
return "user/add";
}
@GetMapping(value = {"user/update"})
public String update(Model model){
model.addAttribute("msg","update,shiro");
return "user/update";
}
@GetMapping(value = {"toLogin"})
public String toLogin(Model model){
return "login";
}
@GetMapping(value = {"login"})
public String login(String username,String password, Model model){
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); // 执行登录的方法,如果没有异常就说明成功
return "index";
} catch (UnknownAccountException e) { // 用户名不存在
model.addAttribute("msg","用户名错误");
e.printStackTrace();
return "login";
} catch (IncorrectCredentialsException e) { // 用户名不存在
model.addAttribute("msg","密码错误");
e.printStackTrace();
return "login";
}
}
@GetMapping("noauth")
@ResponseBody
public String unauthorized(){
return "权限不足";
}
@GetMapping("toLogOut")
public String toLogOut(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
}
package com.example.shiro.config;
import com.example.shiro.dao.UserDao;
import com.example.shiro.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserDao userDao;
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
// String name = "root";
// String password = "123456";
// if (!token.getUsername().equals(name)){
// return null; // 就会抛出异常 UnknownAccountException
// }
// 从数据库中进行获取
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 连接真实数据库
User user = userDao.queryUserByName(token.getUsername());
if (user==null){
return null;
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
// 密码认证,shiro做
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
// ShiroConfig中是定义权限,
// filterChainDefinitionMap.put("/user/add","perms[user:add]");
// 这块儿是赋予用户权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 测试,硬编码
// simpleAuthorizationInfo.addStringPermission("user:add");
Subject subject = SecurityUtils.getSubject();
// AuthenticationInfo 认证的时候就把用户放到Subject中了
User principal = (User) subject.getPrincipal();
// 设置当前用户的权限
simpleAuthorizationInfo.addStringPermission(principal.getPerms());
return simpleAuthorizationInfo;
}
}
package com.example.shiro.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 添加shiro的内置过滤器
/**
* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有记住我功能 才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*
*/
// 由于是链式的,我们这儿用LinkedHashMap
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
// 拦截
// filterChainDefinitionMap.put("/user/add","anon");
// 相当于定义权限,[user:add]就相当于一种权限,这个权限可以访问 /user/add
filterChainDefinitionMap.put("/user/add","perms[user:add]");
filterChainDefinitionMap.put("/user/update","authc");
// 通配符
// filterChainDefinitionMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置登录的请求
// 如果没有权限,就会跳到登录页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
// 设置未授权的请求
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
return shiroFilterFactoryBean;
}
// DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 关联userRealm
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
// 创建 realm 对象,默认对象名就是userRealm
// @Bean(name = "userRealm")
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
// 整合ShiroDialect 用来整合 shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
部分内容转载自:
https://blog.csdn.net/qq_43652509/article/details/88074832
https://www.bilibili.com/video/BV1PE411i7CV?p=41&spm_id_from=pageDriver&vd_source=64c73c596c59837e620fed47fa27ada7
她在陪酒时就已领悟到,在外面还能关心别人的男人,通常都有个幸福的家庭。
嫌疑人X的献身
东野圭吾