【基础框架】Shrio安全框架
基于主页的权限管理(不同用户使用不同的主页,权限通过主页功能菜单进行限制)基于用户和权限的权限管理基于角色的访问控制认证:对用户的身份进行检查(登录验证)授权:对用户的权限进行检查(是否有对应的操作权限)流程示意图:认证,验证用户是否有相应的身份—登录认证;授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;会话管理,用户在认证成功之后创建会话,在没有
Shrio安全框架
一、权限管理
1.1 什么是权限管理?
不同身份的用户进入到系统所能够完成的操作是不相同的,我们对不同用户进行的可执行的操作的管理称之为权限管理。
1.2 如何实现权限管理?
权限管理设计
-
基于主页的权限管理(不同用户使用不同的主页,权限通过主页功能菜单进行限制)
- 适用于权限管理比较单一、用户少、每类用户权限固定
-
基于用户和权限的权限管理
- 可以实现权限的动态分配,但是不够灵活
-
基于角色的访问控制
- RBAC 基于角色的访问控制
二、安全框架简介
2.1 认证授权流程
-
认证:对用户的身份进行检查(登录验证)
-
授权:对用户的权限进行检查(是否有对应的操作权限)
-
流程示意图:
2.2 安全框架
- 帮助我们完成用户身份认证及权限检查功能框架
- 常用的安全框架:
Shiro:Apache Shiro
是一个功能强大并且易用的Java安全框架 (小而简单)Spring Security
:基于Spring的一个安全框架,依赖SpringOAuth2
:第三方授权登录- 自定义安全认证中心
2.3 Shiro
Apache Shiro
是一个功能强大并且易用的Java安全框架- 可以完成用户认证、授权、密码及会话管理
- 可以在任何的应用系统中使用(主要针对单体项目的权限管理)
三、Shiro的工作原理
3.1 Shiro的核心功能
-
Anthentication
认证,验证用户是否有相应的身份—登录认证; -
Authorization
授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作; -
Session Managment
会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用; -
Cryptography
加密,对敏感信息进行加密处理,shiro就提供这种加密机制; -
支持的特性:
Web Support
— Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制Caching
缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率Concurrency
, shiro支持多线程应用Testing
提供测试支持Run As
允许一个用户以另一种身份去访问Remeber Me
-
说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)
3.2 Shiro核心组件
- Shiro三大核心组件:
Subject
、Security Manager
、Realms
- Subject,表示待认证和授权的用户
- Security Manager,它是Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务。
Authenticator
,认证器Anthorizer
,授权器SessionManager
,会话管理器CacheManager
,缓存管理器
- Realm,相当于Shiro进行认证和授权的数据源,充当了Shiro与安全数据之间的“桥梁”或者“连接器”。也就是说,当对用户进行认证(登录)和授权(访问控制)验证时,Shiro会用应用配置的Realm中查找用户及其权限信息。
四、基于JavaSE应用—Shiro的基本使用
4.1 创建Maven项目
4.2 导入Shiro依赖库
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
4.3 创建Shiro配置文件
- 在resource目录下创建名为shiro.ini的文件
- 在文件中完成用户、角色及权限的配置
[users]
zhangsan=123456,seller
lisi=666666,ckmgr
admin=222222,admin
[roles]
admin=*
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
4.4 Shiro的基本使用
package com.qfedu.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import java.util.Scanner;
public class TestShiro {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入帐号:");
String username = scan.nextLine();
System.out.println("请输入密码:");
String password = scan.nextLine();
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.创建realm
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
//3.将realm设置给安全管理器
securityManager.setRealm(iniRealm);
//4.将Realm设置给SecurityUtils工具
SecurityUtils.setSecurityManager(securityManager);
//5.通过SecurityUtils工具类获取subject对象
Subject subject = SecurityUtils.getSubject();
//【认证流程】
//a.将认证帐号和密码封装到token对象中
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//b.通过subject对象调用login方法进行认证申请:
boolean b = false;
try{
subject.login(token);
b = true;
}catch(IncorrectCredentialsException e){
b = false;
}
System.out.println(b?"登录成功":"登录失败");
//【授权】
//判断是否有某个角色
System.out.println(subject.hasRole("seller"));
//判断是否有某个权限
boolean permitted = subject.isPermitted("order-del");
System.out.println(permitted);
}
}
五、整合Shrio
5.1 SpringMVC整合Shrio
-
1.导入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro‐core</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro‐web</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro‐spring</artifactId> <version>1.4.1</version> </dependency>
-
2.在web.xml中添加shrio对应的过滤器配置
-
拦截所有请求
<?xml version="1.0" encoding="UTF‐8"?> <web‐app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web‐app_3_1.xsd" version="3.1"> <context‐param> <param‐name>contextConfigLocation</param‐name> <param‐value>classpath:spring‐*.xml</param‐value> </context‐param> <listener> <listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class> </listener> <servlet> <servlet‐name>springMVC</servlet‐name> <servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class> <init‐param> <param‐name>contextConfigLocation</param‐name> <param‐value>classpath:spring‐context*.xml</param‐value> </init‐param> </servlet> <servlet‐mapping> <servlet‐name>springMVC</servlet‐name> <url‐pattern>*.do</url‐pattern> </servlet‐mapping> <filter> <filter‐name>shiroFilter</filter‐name> <filter‐class>org.springframework.web.filter.DelegatingFilterProxy</filter‐class> <init‐param> <param‐name>targetFilterLifecycle</param‐name> <param‐value>true</param‐value> </init‐param> </filter> <filter‐mapping> <filter‐name>shiroFilter</filter‐name> <url‐pattern>/*</url‐pattern> </filter‐mapping> </web‐app>
-
-
3.在spring配置文件中,配置shrio框架所需的内容
- 创建spring-context-shiro.xml
- 完成shiro配置
<?xml version="1.0" encoding="UTF‐8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring‐beans.xsd"> <!‐‐shiro过滤器‐‐> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"></property> <!‐‐配置登录页面地址,非必须,默认寻找web项目根路径下的/login.jsp‐‐> <property name="loginUrl" value="/login.jsp"></property> <!‐‐配置登录成功之后跳转的页面路径,此配置一般不使用,一般在LoginController中处理 逻辑 <property name="successUrl" value="/index.jsp"></property> ‐‐> <property name="unauthorizedUrl" value="/"></property> <property name="filterChainDefinitions"> <value>/**=anon</value> </property> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <!‐‐证书匹配器‐‐> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean> <!‐‐使用自带的Realm‐‐> <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"></property> <property name="permissionsLookupEnabled" value="true"></property> <property name="dataSource" ref="dataSource"></property> </bean> <!‐‐缓存管理‐‐> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> <!‐‐shiro安全管理器‐‐> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="jdbcRealm"></property> <property name="cacheManager" ref="cacheManager"></property> </bean> </beans>
-
4.配置权限验证使用注解
<!‐‐配置权限验证使用注解‐‐> <aop:config proxy‐target‐class="true"></aop:config> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"></property> </bean>
5.2 SSM整合Shiro进行认证及授权
-
1.创建用户权限表,并添加数据
-
因为此案例目前使用的是shiro默认数据库结构,则需要按照shiro默认的数据表结构进行创建及配置
-
用户信息
create table users( id int primary key auto_increment, username varchar( 60 ) not null, password varchar( 60 ) not null, password_salt varchar( 100 ) );
-
添加用户信息
insert into users(username,password) values('zhangsan','e10adc3949ba59abbe56e057f20f883e'); insert into users(username,password) values('lisi','e10adc3949ba59abbe56e057f20f883e'); insert into users(username,password) values('wangwu','e10adc3949ba59abbe56e057f20f883e'); insert into users(username,password) values('zhaoliu','e10adc3949ba59abbe56e057f20f883e'); insert into users(username,password) values('chenqi','e10adc3949ba59abbe56e057f20f883e');
- 角色信息
create table user_roles( id int primary key auto_increment, username varchar( 60 ) not null, role_name varchar( 100 ) not null ); ‐‐ 添加用户角色 insert into user_roles(username,role_name) values('zhangsan','admin'); insert into user_roles(username,role_name) values('lisi','user'); insert into user_roles(username,role_name) values('wangwu','cmanager'); insert into user_roles(username,role_name) values('zhaoliu','mmanager'); insert into user_roles(username,role_name) values('chenqi','xmanager');
- 权限信息
create table roles_permissions( id int primary key auto_increment, role_name varchar( 100 ) not null, permission varchar( 100 ) not null ); ‐‐ 添加权限信息 insert into roles_permissions(role_name,permission) values('admin','*'); insert into roles_permissions(role_name,permission) values('user','sys:*:find'); insert into roles_permissions(role_name,permission) values('cmanager','sys:c:save'); insert into roles_permissions(role_name,permission) values('cmanager','sys:c:delete'); insert into roles_permissions(role_name,permission) values('cmanager','sys:c:update'); insert into roles_permissions(role_name,permission) values('cmanager','sys:c:find'); insert into roles_permissions(role_name,permission) values('mmanager','sys:m:save'); insert into roles_permissions(role_name,permission) values('mmanager','sys:m:delete'); insert into roles_permissions(role_name,permission) values('mmanager','sys:m:update'); insert into roles_permissions(role_name,permission) values('mmanager','sys:m:find'); insert into roles_permissions(role_name,permission) values('xmanager','sys:x:save'); insert into roles_permissions(role_name,permission) values('xmanager','sys:x:delete'); insert into roles_permissions(role_name,permission) values('xmanager','sys:x:update'); insert into roles_permissions(role_name,permission) values('xmanager','sys:x:find');
-
- 2.Service层
- UserService
@Service
public class UserService {
public void login(String userName,String userPwd) throws Exception{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
subject.login(token);
}
}
- 3.Controller层
- UserController
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login.do")
public String login(String userName, String userPwd, Model model){
try{
userService.login(userName,userPwd);
System.out.println("‐‐‐‐success");
return "index.jsp";
}catch (Exception e){
System.out.println("‐‐‐‐fail");
model.addAttribute("tips","登录验证失败,请重试!");
return "login.jsp";
}
}
}
- 4.View层
- login.jsp
<%@ page contentType="text/html;charset=UTF‐8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${tips}
<form action="login.do" method="post">
<input type="text" name="userName"/><br/>
<input type="password" name="userPwd"/><br/>
<button type="submit">提交</button>
</form>
</body>
</html>
- index.jsp
<%@ page contentType="text/html;charset=UTF‐8" language="java" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<shiro:principal/>,欢迎您!
[用户角色:<shiro:hasRole name="admin">admin</shiro:hasRole>
<shiro:hasRole name="user">user</shiro:hasRole>
<shiro:hasRole name="cmanager">cmanager</shiro:hasRole>
<shiro:hasRole name="mmanager">mmanager</shiro:hasRole>
<shiro:hasRole name="xmanager">xmanager</shiro:hasRole>]
<hr/>
C模块
<ul>
<shiro:hasPermission name="sys:c:save"><li>添加C</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:c:delete"><li>删除C</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:c:update"><li>修改C</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:c:find"><li>查询C</li>
</shiro:hasPermission>
</ul>
X模块
<ul>
<shiro:hasPermission name="sys:x:save"><li>添加X</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:x:delete"><li>删除X</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:x:update"><li>修改X</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:x:find"><li>查询X</li>
</shiro:hasPermission>
</ul>
M模块
<ul>
<shiro:hasPermission name="sys:m:save"><li>添加M</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:delete"><li>删除M</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:update"><li>修改M</li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:find"><li>查询M</li>
</shiro:hasPermission>
</ul>
</body>
</html>
5.3 SpringBoot应用整合Shiro
- JavaSE应用中使用
- web应用中使用
- SSM整合Shiro(配置多,用的少)
- SpringBoot应用整合Shiro
1. 创建SpringBoot应用
- lombok
- spring web
- thymeleaf
2. 整合Druid和MyBatis
-
依赖
<!-- druid starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>
-
配置
spring: datasource: druid: url: jdbc:mysql://47.96.11.185:3306/test # MySQL如果是8.x com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver username: root password: admin123 initial-size: 1 min-idle: 1 max-active: 20 mybatis: mapper-locations: classpath:mappers/*Mapper.xml type-aliases-package: com.qfedu.springbootssm.beans
3. 整合Shiro
-
导入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
-
Shiro配置(java配置方式)
- SpringBoot默认没有提供对Shiro的自动配置
@Configuration public class ShiroConfig { @Bean public IniRealm getIniRealm(){ IniRealm iniRealm = new IniRealm("classpath:shiro.ini"); return iniRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(IniRealm iniRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager要完成校验,需要realm securityManager.setRealm(iniRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的 filter.setSecurityManager(securityManager); //设置shiro的拦截规则 // anon 匿名用户可访问 // authc 认证用户可访问 // user 使用RemeberMe的用户可访问 // perms 对应权限可访问 // role 对应的角色可访问 Map<String,String> filterMap = new HashMap<>(); filterMap.put("/","anon"); filterMap.put("/login.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/static/**","anon"); filterMap.put("/**","authc"); filter.setFilterChainDefinitionMap(filterMap); filter.setLoginUrl("/login.html"); //设置未授权访问的页面路径 filter.setUnauthorizedUrl("/login.html"); return filter; } }
-
认证测试
- UserServiceImpl.java
@Service public class UserServiceImpl { public void checkLogin(String userName,String userPwd) throws Exception{ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); subject.login(token); } }
- UserController.java
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("login") public String login(String userName,String userPwd){ try { userService.checkLogin(userName,userPwd); System.out.println("------登录成功!"); return "index"; } catch (Exception e) { System.out.println("------登录失败!"); return "login"; } } }
-
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
login
<hr/>
<form action="user/login">
<p>帐号:<input type="text" name="userName"/></p>
<p>密码:<input type="text" name="userPwd"/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
六、SpringBoot应用整合Shiro—案例(JdbcRealm)
6.1 JdbcRealm介绍
- 如果使用JdbcRealm,则必须提供JdbcRealm所需的表结构(权限设计)
6.2 JdbcRealm规定的表结构
-
用户信息表: users
create table users( id int primary key auto_increment, username varchar(60) not null unique, password varchar(20) not null, password_salt varchar(20) ); insert into users(username,password) values('zhangsan','123456'); insert into users(username,password) values('lisi','123456'); insert into users(username,password) values('wangwu','123456'); insert into users(username,password) values('zhaoliu','123456'); insert into users(username,password) values('chenqi','123456');
-
角色信息表: user_roles
create table user_roles( id int primary key auto_increment, username varchar(60) not null, role_name varchar(100) not null ); -- admin系统管理员 -- cmanager 库管人员 -- xmanager 销售人员 -- kmanager 客服人员 -- zmanager 行政人员 insert into user_roles(username,role_name) values('zhangsan','admin'); insert into user_roles(username,role_name) values('lisi','cmanager'); insert into user_roles(username,role_name) values('wangwu','xmanager'); insert into user_roles(username,role_name) values('zhaoliu','kmanager'); insert into user_roles(username,role_name) values('chenqi','zmanager');
-
权限信息表:roles_permissions
create table roles_permissions( id int primary key auto_increment, role_name varchar(100) not null, permission varchar(100) not null ); -- 权限 sys:c:save sys:c:delete... -- 管理员具备所有权限 insert into roles_permissions(role_name,permission) values("admin","*"); -- 库管人员 insert into roles_permissions(role_name,permission) values("cmanager","sys:c:save"); insert into roles_permissions(role_name,permission) values("cmanager","sys:c:delete"); insert into roles_permissions(role_name,permission) values("cmanager","sys:c:update"); insert into roles_permissions(role_name,permission) values("cmanager","sys:c:find"); -- 销售人员 insert into roles_permissions(role_name,permission) values("xmanager","sys:c:find"); insert into roles_permissions(role_name,permission) values("xmanager","sys:x:save"); insert into roles_permissions(role_name,permission) values("xmanager","sys:x:delete"); insert into roles_permissions(role_name,permission) values("xmanager","sys:x:update"); insert into roles_permissions(role_name,permission) values("xmanager","sys:x:find"); insert into roles_permissions(role_name,permission) values("xmanager","sys:k:save"); insert into roles_permissions(role_name,permission) values("xmanager","sys:k:delete"); insert into roles_permissions(role_name,permission) values("xmanager","sys:k:update"); insert into roles_permissions(role_name,permission) values("xmanager","sys:k:find"); -- 客服人员 insert into roles_permissions(role_name,permission) values("kmanager","sys:k:find"); insert into roles_permissions(role_name,permission) values("kmanager","sys:k:update"); -- 新增人员 insert into roles_permissions(role_name,permission) values("zmanager","sys:*:find");
6.3 SpringBoot整合Shiro
-
创建SpringBoot应用
-
整合Druid和MyBatis
-
整合shiro
-
添加依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
-
配置Shiro
@Configuration public class ShiroConfig { @Bean public JdbcRealm getJdbcRealm(DataSource dataSource){ JdbcRealm jdbcRealm = new JdbcRealm(); //JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范) jdbcRealm.setDataSource(dataSource); //JdbcRealm默认开启认证功能,需要手动开启授权功能 jdbcRealm.setPermissionsLookupEnabled(true); return jdbcRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jdbcRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的 filter.setSecurityManager(securityManager); Map<String,String> filterMap = new HashMap<>(); filterMap.put("/","anon"); filterMap.put("/login.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/static/**","anon"); filterMap.put("/**","authc"); filter.setFilterChainDefinitionMap(filterMap); filter.setLoginUrl("/login.html"); //设置未授权访问的页面路径 filter.setUnauthorizedUrl("/login.html"); return filter; } }
3.4 认证功能测试
略
-
6.4 Shiro的标签使用
当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;Shiro就提供了一套标签用于在页面来进行权限数据的呈现
-
Shiro提供了可供JSP使用的标签以及Thymeleaf中标签
-
JSP页面中引用:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
-
Thymeleaf模版中引用:
- 在pom.xml文件中导入thymeleaf模版对shiro标签支持的依赖
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
- 在ShiroConfig中配置Shiro的
@Configuration public class ShiroConfig { @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } //... }
- Thymeleaf模版中引入shiro的命名空间
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> ... </html>
-
常用标签
-
guest,判断用户是否是游客身份,如果是游客身份则显示此标签内容
<shiro:guest> 欢迎游客访问,<a href="login.html">登录</a> </shiro:guest>
-
user,判断用户是否是认证身份,如果是认证身份则显示此标签内容
-
principal,获取当前登录用户名
<shiro:user> 用户[<shiro:principal/>]欢迎您! </shiro:user>
-
notAuthenticated/authenticated
-
hasRole
-
hasPermission
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> index <hr/> <shiro:guest> 欢迎游客访问,<a href="login.html">登录</a> </shiro:guest> <shiro:user> 用户[<shiro:principal/>]欢迎您! 当前用户为<shiro:hasRole name="admin">超级管理员</shiro:hasRole> <shiro:hasRole name="cmanager">仓管人员</shiro:hasRole> <shiro:hasRole name="xmanager">销售人员</shiro:hasRole> <shiro:hasRole name="kmanager">客服人员</shiro:hasRole> <shiro:hasRole name="zmanager">行政人员</shiro:hasRole> </shiro:user> <hr/> 仓库管理 <ul> <shiro:hasPermission name="sys:c:save"><li><a href="#">入库</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:c:delete"><li><a href="#">出库</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:c:update"><li><a href="#">修改</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:c:find"><li><a href="#">查询</a></li></shiro:hasPermission> </ul> 订单管理 <ul> <shiro:hasPermission name="sys:x:save"><li><a href="#">添加订单</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:x:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:x:update"><li><a href="#">修改订单</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:x:find"><li><a href="#">查询订单</a></li></shiro:hasPermission> </ul> 客户管理 <ul> <shiro:hasPermission name="sys:k:save"><li><a href="#">添加客户</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:k:delete"><li><a href="#">删除客户</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:k:update"><li><a href="#">修改客户</a></li></shiro:hasPermission> <shiro:hasPermission name="sys:k:find"><li><a href="#">查询客户</a></li></shiro:hasPermission> </ul> </body> </html>
七、SpringBoot整合Shiro完成权限管理案例—自定义Realm
使用JdbcRealm可以完成用户权限管理,但是我们必须提供JdbcRealm规定的数据表结构;如果在我们的项目开发中 ,这个JdbcRealm规定的数据表结构不能满足开发需求,该如何处理呢?
- 自定义数据库表结构
- 自定义Realm实现认证和授权
7.1 数据库设计
-
RBAC基于角色的访问控制
-- 用户信息表 create table tb_users( user_id int primary key auto_increment, username varchar(60) not null unique, password varchar(20) not null, password_salt varchar(60) ); insert into tb_users(username,password) values('zhangsan','123456'); insert into tb_users(username,password) values('lisi','123456'); insert into tb_users(username,password) values('wangwu','123456'); insert into tb_users(username,password) values('zhaoliu','123456'); insert into tb_users(username,password) values('chenqi','123456'); -- 角色信息表 create table tb_roles( role_id int primary key auto_increment, role_name varchar(60) not null ); insert into tb_roles(role_name) values('admin'); insert into tb_roles(role_name) values('cmanager'); -- 仓管 insert into tb_roles(role_name) values('xmanager'); -- 销售 insert into tb_roles(role_name) values('kmanager'); -- 客服 insert into tb_roles(role_name) values('zmanager'); -- 行政 -- 权限信息表 create table tb_permissions( permission_id int primary key auto_increment, -- 1 permission_code varchar(60) not null, -- sys:c:find permission_name varchar(60) -- 仓库查询 ); insert into tb_permissions(permission_code,permission_name) values('sys:c:save','入库'); insert into tb_permissions(permission_code,permission_name) values('sys:c:delete','出库'); insert into tb_permissions(permission_code,permission_name) values('sys:c:update','修改'); insert into tb_permissions(permission_code,permission_name) values('sys:c:find','查询'); insert into tb_permissions(permission_code,permission_name) values('sys:x:save','新增订单'); insert into tb_permissions(permission_code,permission_name) values('sys:x:delete','删除订单'); insert into tb_permissions(permission_code,permission_name) values('sys:x:update','修改订单'); insert into tb_permissions(permission_code,permission_name) values('sys:x:find','查询订单'); insert into tb_permissions(permission_code,permission_name) values('sys:k:save','新增客户'); insert into tb_permissions(permission_code,permission_name) values('sys:k:delete','删除客户'); insert into tb_permissions(permission_code,permission_name) values('sys:k:update','修改客户'); insert into tb_permissions(permission_code,permission_name) values('sys:k:find','查询客户'); -- 用户角色表 create table tb_urs( uid int not null, rid int not null -- primary key(uid,rid), -- constraint FK_user foreign key(uid) references tb_users(user_id), -- constraint FK_role foreign key(rid) references tb_roles(role_id) ); insert into tb_urs(uid,rid) values(1,1); insert into tb_urs(uid,rid) values(1,2); insert into tb_urs(uid,rid) values(1,3); insert into tb_urs(uid,rid) values(1,4); insert into tb_urs(uid,rid) values(1,5); insert into tb_urs(uid,rid) values(2,2); insert into tb_urs(uid,rid) values(3,3); insert into tb_urs(uid,rid) values(4,4); insert into tb_urs(uid,rid) values(5,5); -- 角色权限表 create table tb_rps( rid int not null, pid int not null ); -- 给仓管角色分配权限 insert into tb_rps(rid,pid) values(2,1); insert into tb_rps(rid,pid) values(2,2); insert into tb_rps(rid,pid) values(2,3); insert into tb_rps(rid,pid) values(2,4); -- 给销售角色分配权限 insert into tb_rps(rid,pid) values(3,4); insert into tb_rps(rid,pid) values(3,5); insert into tb_rps(rid,pid) values(3,6); insert into tb_rps(rid,pid) values(3,7); insert into tb_rps(rid,pid) values(3,8); insert into tb_rps(rid,pid) values(3,9); insert into tb_rps(rid,pid) values(3,10); insert into tb_rps(rid,pid) values(3,11); insert into tb_rps(rid,pid) values(3,12); -- 给客服角色分配权限 insert into tb_rps(rid,pid) values(4,11); insert into tb_rps(rid,pid) values(4,12); -- 给行政角色分配权限 insert into tb_rps(rid,pid) values(5,4); insert into tb_rps(rid,pid) values(5,8); insert into tb_rps(rid,pid) values(5,12);
7.2 DAO实现
Shiro进行认证需要用户信息:
- 根据用户名查询用户信息
Shiro进行授权管理需要当前用户的角色和权限
根据用户名查询当前用户的角色列表(3张表连接查询)
根据用户名查询当前用户的权限列表(5张表连接查询)
7.2.1 创建SpringBoot项目,整合MyBatis
7.2.2 根据用户名查询用户信息
- 创建BeanBean
@Data
public class User {
private Integer userId;
private String userName;
private String userPwd;
private String pwdSalt;
}
- 创建DAO
public interface UserDAO {
public User queryUserByUsername(String username) throws Exception;
}
- 映射配置
<?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.qfedu.shiro4.dao.UserDAO">
<resultMap id="userMap" type="User">
<id column="user_id" property="userId"></id>
<result column="username" property="userName"/>
<result column="password" property="userPwd"/>
<result column="password_salt" property="pwdSalt"/>
</resultMap>
<select id="queryUserByUsername" resultMap="userMap">
select * from tb_users
where username=#{username}
</select>
</mapper>
7.2.3 根据用户名查询角色名列表
- 创建DAO
public interface RoleDAO {
public Set<String> queryRoleNamesByUsername(String username) throws Exception;
}
- 映射配置
<?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.qfedu.shiro4.dao.RoleDAO">
<select id="queryRoleNamesByUsername" resultSets="java.util.Set" resultType="string">
select role_name
from tb_users inner join tb_urs
on tb_users.user_id = tb_urs.uid
inner join tb_roles
on tb_urs.rid = tb_roles.role_id
where tb_users.username=#{username}
</select>
</mapper>
7.2.4 根据用户名查询权限列表
- 创建DAO
public interface PermissionDAO {
public Set<String> queryPermissionsByUsername(String username) throws Exception;
}
- 映射配置
<?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.qfedu.shiro4.dao.PermissionDAO">
<select id="queryPermissionsByUsername" resultSets="java.util.Set" resultType="string">
select tb_permissions.permission_code from tb_users
inner join tb_urs on tb_users.user_id=tb_urs.uid
inner join tb_roles on tb_urs.rid=tb_roles.role_id
inner join tb_rps on tb_roles.role_id=tb_rps.rid
inner join tb_permissions on tb_rps.pid=tb_permissions.permission_id
where tb_users.username=#{username}
</select>
</mapper>
7.3 整合Shiro
- 导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 配置Shiro-基于Java配置方式
@Configuration
public class ShiroConfig {
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
//自定义Realm
@Bean
public MyRealm getMyRealm(){
MyRealm myRealm = new MyRealm();
return myRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
//过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
filter.setSecurityManager(securityManager);
Map<String,String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/index.html","anon");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/layui/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
//设置未授权访问的页面路径()
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
- 自定义Realm
/**
* 1.创建一个类继承AuthorizingRealm类(实现了Realm接口的类)
* 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo方法
* 3.重写getName方法返回当前realm的一个自定义名称
*/
public class MyRealm extends AuthorizingRealm {
@Resource
private UserDAO userDAO;
@Resource
private RoleDAO roleDAO;
@Resource
private PermissionDAO permissionDAO;
public String getName() {
return "myRealm";
}
/**
* 获取授权数据(将当前用户的角色及权限信息查询出来)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户的用户名
String username = (String) principalCollection.iterator().next();
//根据用户名查询当前用户的角色列表
Set<String> roleNames = roleDAO.queryRoleNamesByUsername(username);
//根据用户名查询当前用户的权限列表
Set<String> ps = permissionDAO.queryPermissionsByUsername(username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roleNames);
info.setStringPermissions(ps);
return info;
}
/**
* 获取认证的安全数据(从数据库查询的用户的正确数据)
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//参数authenticationToken就是传递的 subject.login(token)
// 从token中获取用户名
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//根据用户名,从数据库查询当前用户的安全数据
User user = userDAO.queryUserByUsername(username);
AuthenticationInfo info = new SimpleAuthenticationInfo(
username, //当前用户用户名
user.getUserPwd(), //从数据库查询出来的安全密码
getName());
return info;
}
}
7.4 SpringBoot应用打包部署
- SpringBoot项目集成了web容器(Tomcat),所以SpringBoot应用是可以打包成jar直接运行的
八、加密加盐
- 明文-----(加密规则)-----密文
- 加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式
- BASE64:可反编码的编码方式(对称)
- 明文----密文
- 密文----明文
- MD5: 不可逆的编码方式(非对称)
- 明文----密文
- 如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
- 使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证。
8.1 加密介绍
8.2 Shiro使用加密认证
-
配置matcher
@Configuration public class ShiroConfig { //... @Bean public HashedCredentialsMatcher getHashedCredentialsMatcher(){ HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //matcher就是用来指定加密规则 //加密方式 matcher.setHashAlgorithmName("md5"); //hash次数 matcher.setHashIterations(1); //此处的循环次数要与用户注册是密码加密次数一致 return matcher; } //自定义Realm @Bean public MyRealm getMyRealm( HashedCredentialsMatcher matcher ){ MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(matcher); return myRealm; } //... }
8.3 用户注册密码加密处理
-
registh.html
<form action="/user/regist" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="submit" value="提交注册"/></p> </form>
-
UserController
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("/regist") public String regist(String userName,String userPwd) { System.out.println("------注册"); //注册的时候要对密码进行加密存储 Md5Hash md5Hash = new Md5Hash(userPwd); System.out.println("--->>>"+ md5Hash.toHex()); //加盐加密 int num = new Random().nextInt(90000)+10000; //10000—99999 String salt = num+""; Md5Hash md5Hash2 = new Md5Hash(userPwd,salt); System.out.println("--->>>"+md5Hash2); //加盐加密+多次hash Md5Hash md5Hash3 = new Md5Hash(userPwd,salt,3); System.out.println("--->>>"+md5Hash3); //SimpleHash hash = new SimpleHash("md5",userPwd,num,3); //将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,盐也要保存 return "login"; } }
8.4 如果密码进行了加盐处理,则Realm在返回认证数据时需要返回盐
-
在自定义Realm中:
public class MyRealm extends AuthorizingRealm { @Resource private UserDAO userDAO; @Resource private RoleDAO roleDAO; @Resource private PermissionDAO permissionDAO; public String getName() { return "myRealm"; } /** * 获取认证的安全数据(从数据库查询的用户的正确数据) */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的 subject.login(token) // 从token中获取用户名 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据用户名,从数据库查询当前用户的安全数据 User user = userDAO.queryUserByUsername(username); // AuthenticationInfo info = new SimpleAuthenticationInfo( // username, //当前用户用户名 // user.getUserPwd(), //从数据库查询出来的安全密码 // getName()); //如果数据库中用户的密码是加了盐的 AuthenticationInfo info = new SimpleAuthenticationInfo( username, //当前用户用户名 user.getUserPwd(), //从数据库查询出来的安全密码 ByteSource.Util.bytes(user.getPwdSalt()), getName()); return info; } }
九、退出登录
-
在Shiro过滤器中进行配置,配置logut对应的路径
filterMap.put("/exit","logout");
-
在页面的“退出”按钮上,跳转到logout对应的url
<a href="exit">退出</a>
十、授权管理
用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查—授权
权限控制通常有两类做法:
- 不同身份的用户登录,我们现在不同的操作菜单(没有权限的菜单不现实)
- 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
10.1 HTML授权
-
在菜单页面只显示当前用户拥有权限操作的菜单
-
shiro标签
<shiro:hasPermission name="sys:c:save"> <dd><a href="javascript:;">入库</a></dd> </shiro:hasPermission>
10.2 过滤器授权
-
在shiro过滤器中对请求的url进行权限设置
filterMap.put("/c_add.html","perms[sys:c:save]"); //设置未授权访问的页面路径—当权限不足时显示此页面 filter.setUnauthorizedUrl("/lesspermission.html");
10.3 注解授权
-
配置Spring对Shiro注解的支持:ShiroConfig.java
@Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); return autoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
-
在请求的控制器添加权限注解
@Controller @RequestMapping("customer") public class CustomerController { @RequestMapping("list") //如果没有 sys:k:find 权限,则不允许执行此方法 @RequiresPermissions("sys:k:find") // @RequiresRoles("") public String list(){ System.out.println("----------->查询客户信息"); return "customer_list"; } }
-
通过全局异常处理,指定权限不足时的页面跳转
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public String doException(Exception e){ if(e instanceof AuthorizationException){ return "lesspermission"; } return null; } }
10.4 手动授权
-
在代码中进行手动的权限校验
Subject subject = SecurityUtils.getSubject(); if(subject.isPermitted("sys:k:find")){ System.out.println("----------->查询客户信息"); return "customer_list"; }else{ return "lesspermission"; }
十一、缓存使用
使用Shiro进行权限管理过程中,每次授权都会访问realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力
Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)
11.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
11.2 配置缓存策略
- 在resources目录下创建一个xml文件(ehcache.xml)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="C:\TEMP" />
<cache name="users" timeToLiveSeconds="300" maxEntriesLocalHeap="1000"/>
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据
LRU 最近最少使用
FIFO 先进先出
LFU 最少使用
-->
</ehcache>
11.3 加入缓存管理
- ShiroConfig.java
@Bean
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
return securityManager;
}
十二、session管理
Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理
-
如果我们需要对session进行管理
- 自定义session管理器
- 将自定义的session管理器设置给SecurityManager
-
配置自定义SessionManager:ShiroConfig.java
@Bean public DefaultWebSessionManager getDefaultWebSessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000 //配置sessionManager sessionManager.setGlobalSessionTimeout(5*60*1000); return sessionManager; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setCacheManager(getEhCacheManager()); securityManager.setSessionManager(getDefaultWebSessionManager()); return securityManager; }
解疑
-
如何避免登录页面显示在页面框架中?
-
在登录页面添如下JS代码:
<script type="text/javascript"> //页面框架 if(window.top != window.self){ window.top.location = window.self.location; } </script>
十三、RememberMe
将用户对页面访问的权限分为三个级别:
- 未认证—可访问的页面—(陌生人)—问候
- login.html、regist.html
- 记住我—可访问的页面—(前女友)—朋友间的拥抱
- info.html
- 已认证—可访问的页面—(现女友)—牵手
- 转账.html
13.1 在过滤器中设置“记住我”可访问的url
// anon 表示未认证可访问的url
// user 表示记住我可访问的url(已认证也可以访问)
//authc 表示已认证可访问的url
//perms 表示必须具备指定的权限才可访问
//logout 表示指定退出的url
filterMap.put("/","anon");
filterMap.put("/index.html","user");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/layui/**","anon");
filterMap.put("/**","authc");
filterMap.put("/c_add.html","perms[sys:c:save]");
filterMap.put("/exit","logout");
13.2 在ShiroConfig.java中配置基于cookie的rememberMe管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
//cookie必须设置name
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(30*24*60*60);
rememberMeManager.setCookie(cookie);
return rememberMeManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
securityManager.setSessionManager(getDefaultWebSessionManager());
//设置remember管理器
securityManager.setRememberMeManager(cookieRememberMeManager());
return securityManager;
}
13.3 登录认证时设置token“记住我”
- 登录页面
<form action="/user/login" method="post">
<p>帐号:<input type="text" name="userName"/></p>
<p>密码:<input type="text" name="userPwd"/></p>
<p>记住我:<input type="checkbox" name="rememberMe"/></p>
<p><input type="submit" value="登录"/></p>
</form>
- 控制器
@Controller
@RequestMapping("user")
public class UserController {
@Resource
private UserServiceImpl userService;
@RequestMapping("login")
public String login(String userName,String userPwd,boolean rememberMe){
try {
userService.checkLogin(userName,userPwd,rememberMe);
System.out.println("------登录成功!");
return "index";
} catch (Exception e) {
System.out.println("------登录失败!");
return "login";
}
}
//...
}
- service
@Service
public class UserServiceImpl {
public void checkLogin(String userName, String userPwd,boolean rememberMe) throws Exception {
//Shiro进行认证 ——入口
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
token.setRememberMe(rememberMe);
subject.login(token);
}
}
十四、Shiro多Realm配置
14.1 使用场景
- 当shiro进行权限管理,数据来自于不同的数据源时,我们可以给SecurityManager配置多个Realm
14.2 多个Realm的处理方式
14.2.1 链式处理
- 多个Realm依次进行认证
14.2.2 分支处理
- 根据不同的条件从多个Realm中选择一个进行认证处理
14.3 多Realm配置(链式处理)
-
定义多个Realm
-
UserRealm
public class UserRealm extends AuthorizingRealm { Logger logger = LoggerFactory.getLogger(UserRealm.class); @Override public String getName() { return "UserRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("--------------------------------UserRealm"); //从token中获取username UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据username从users表中查询用户信息 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName()); return info; } }
-
ManagerRealm
public class ManagerRealm extends AuthorizingRealm { Logger logger = LoggerFactory.getLogger(ManagerRealm.class); @Override public String getName() { return "ManagerRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("--------------------------------ManagerRealm"); //从token中获取username UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据username从吗managers表中查询用户信息 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"222222",getName()); return info; } }
-
-
在ShiroConfig.java中为SecurityManager配置多个Realm
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //... }
-
测试代码:
-
login.html
<form action="user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="radio" name="loginType" value="User"/>普通用户 <input type="radio" name="loginType" value="Manager"/>管理员</p> <p><input type="submit" value="登录"/></p> </form>
-
UserController.java
@Controller @RequestMapping("user") public class UserController { Logger logger = LoggerFactory.getLogger(UserController.class); @RequestMapping("login") public String login(String userName,String userPwd, String loginType){ logger.info("~~~~~~~~~~~~~UserController-login"); try{ UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); Subject subject = SecurityUtils.getSubject(); subject.login(token); return "index"; }catch (Exception e){ return "login"; } } }
-
14.4 Shiro认证处理源码分析
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
// this.doMultiRealmAuthentication(realms, authenticationToken);中的realms参数就是认证会执行的Realm
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
14.5 多Realm配置(分支处理)
根据不同的条件执行不同的Realm
-
流程分析
-
实现案例:用户不同身份登录执行不同的Realm
-
自定义Realm(UserRealm\ManagerRealm)
- 当在登录页面选择“普通用户”登录,则执行UserRealm的认证
- 当在登录页面选择“管理员”登录,则执行ManagerRealm的认证
-
Realm的声明及配置
-
自定义Token
public class MyToken extends UsernamePasswordToken { private String loginType; public MyToken(String userName,String userPwd, String loginType) { super(userName,userPwd); this.loginType = loginType; } public String getLoginType() { return loginType; } public void setLoginType(String loginType) { this.loginType = loginType; } }
-
自定义认证器
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { Logger logger = LoggerFactory.getLogger(MyModularRealmAuthenticator.class); @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("------------------------------MyModularRealmAuthenticator"); this.assertRealmsConfigured(); Collection<Realm> realms = this.getRealms(); MyToken token = (MyToken) authenticationToken; String loginType = token.getLoginType(); // User logger.info("------------------------------loginType:"+loginType); Collection<Realm> typeRealms = new ArrayList<>(); for(Realm realm:realms){ if(realm.getName().startsWith(loginType)){ //UserRealm typeRealms.add(realm); } } if(typeRealms.size()==1){ return this.doSingleRealmAuthentication((Realm)typeRealms.iterator().next(), authenticationToken); }else{ return this.doMultiRealmAuthentication(typeRealms, authenticationToken); } } }
-
配置自定义认证器
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public MyModularRealmAuthenticator myModularRealmAuthenticator(){ MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator(); return myModularRealmAuthenticator; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //配置自定义认证器(放在realms设置之前) securityManager.setAuthenticator(myModularRealmAuthenticator()); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //... }
-
测试:控制器接受数据进行认证
- login.html
<form action="user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="radio" name="loginType" value="User" checked/>普通用户 <input type="radio" name="loginType" value="Manager"/>管理员</p> <p><input type="submit" value="登录"/></p> </form>
- UserController.java
@Controller @RequestMapping("user") public class UserController { Logger logger = LoggerFactory.getLogger(UserController.class); @RequestMapping("login") public String login(String userName,String userPwd, String loginType){ logger.info("~~~~~~~~~~~~~UserController-login"); try{ //UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); MyToken token = new MyToken(userName,userPwd,loginType); Subject subject = SecurityUtils.getSubject(); subject.login(token); return "index"; }catch (Exception e){ return "login"; } } }
-
附件 单体项目开发技术清单
1. JSP应用
-
View JSP(Java Server Page)
-
Control Servlet
-
Model JDBC(Java Database Connection)
-
第一阶段的项目:JSP/Servlet+JDBC
2. SSM
- View JSP
- Control SpringMVC
- Model MyBatis
- 第二阶段项目:JSP+SSM
3. SpringBoot
- View Thymeleaf
- Control SpringMVC
- Model MyBatis/tkMapper
- 第三阶段练习项目:thymeleaf+SpringBoot(SSM)
单体项目:项目的前端页面与服务端的代码在同一个项目中(部署在同一个服务器上)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)