1.1 什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
1.2 什么是身份认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
1.3 什么是授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
2. shiro安全框架
2.1 什么是shiro
Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架spring security,Shiro 要简单的多.
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
2.2 为什么学shiro
Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。
2.3 shiro中的认证
2.3.1 认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
2.3.2 shiro中认证的关键对象
Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,
一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
2.3.3 认证流程
2.4 Shiro.ini文件
2.4.1 什么是ini文件
ini (InitializationFile) 初始文件.Window系统文件扩展名.
2.4.2 为什么使用ini文件
Shiro 使用时可以连接数据库,也可以不连接数据库.
如果不连接数据库,可以在shiro.ini中配置静态数据
2.4.3 Shiro.ini文件的组成部分
[users] :定义用户名和密码
[users] ---->表示user表
# 定义用户名为zhangsan 密码为zs
zhangsan=zs //账号=密码
# 定义用户名lisi密码为lisi同时具有role1和role2两个角色
lisi=lisi,role1,role2
---------------------------------------------------------------
[roles]: 定义角色
[roles]
role1=权限名1,权限名2
role2=权限3,权限4
如
[roles]
role1=user:query,user:add,user:update,user:delete,user:export
role2=user:query,user:add
[urls] : 定义哪些内置urls生效.在web应用时使用.
[urls]
#url地址=内置filter或自定义filter
# 访问时出现/login的url必须去认证.支持authc对应的Filter
/login=authc
# 任意的url都不需要进行认证等功能.
/** = anon
# 所有的内容都必须保证用户已经登录.
/**=user
# url abc 访问时必须保证用户具有role1和role2角色.
/abc=roles[“role1,role2”]
authc 代表必须认证之后才能访问的路径
anon 任意的url都不需要进行认证等功能
user 所有的内容都必须保证用户已经登录.
logout 注销
2.5 Shiro实现认证_ini
2.5.1 认证流程
流程如下:
1、 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,
调用之前必须通过 SecurityUtils.setSecurityManager() 设置;
2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,
此处可以自定义插入自己的实现;
4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,
默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,
如果没有返回 / 抛出异常表示身份验证成功了。
此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
2.5.2 使用shiro的ini文件完成认证功能
(1) 创建普通的maven项目并引入shiro-core的依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
(2)在resources下创建shiro.ini
[users]
admin=123456
zhangsan=123456
(3)测试
public class demo01 {
public static void main(String[] args) {
//创建securityManager对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置securityManager对象的Realm对象
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
//3.SecurityUtils给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject 主体
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken admin = new UsernamePasswordToken("admin", "123456");
//5.调用主体的登陆方法
subject.login(admin);
System.out.println("登陆成功");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("账号或者密码错误");
}
}
}
4.常见的异常类型
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
2.5.2 使用shiro的ini文件完成授权功能
授权的功能是在认证的基础上完成的
(1)修改ini文件
[users]
admin=123456,role1,role2
zhangsan=123456,role1
[roles]
role1=user:query,user:export
role2=user:delete,user:update,user:add
测试:
public class demo01 {
public static void main(String[] args) {
//创建securityManager对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置securityManager对象的Realm对象
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
//3.SecurityUtils给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject 主体
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken admin = new UsernamePasswordToken("admin", "123456");
//5.调用主体的登陆方法
subject.login(admin);
System.out.println("登陆成功");
System.out.println(subject.isPermitted("user:add"));
System.out.println(subject.isPermitted("user:del"));
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("账号或者密码错误");
}
}
}
2.6 自定义认证
https://blog.csdn.net/weixin_44449838/article/details/108712641
这个是认证的底层源码
核心代码
我们分析源码 我们的最终认证交于realm完成认证功能,返回一个info, 如果info不为null,则进行密码比对。如果我们希望用数据库中的账号和密码完成认证功能,则只需要我们自定义一个类并继承AuthenticatingRealm。
密码加密是通过md5hash实现的
自定义realm完成认证功能
public class MyRealm extends AuthenticatingRealm {
UserService userService = new UserService();
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//根据token获取账号
String username = (String)authenticationToken.getPrincipal();
//根据账号查询
User user = userService.findByUsername(username);
if(user!=null){
//获取密码
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(), salt,this.getName());
return info;
}
return null;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String pwd;
private String salt;
}
public class UserService {
public User findByUsername(String username){
if("aaa".equals(username)){
return new User(1,"aaa","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}else if("bbb".equals(username)){
return new User(2,"bbb","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}
return null;
}
}
测试:
public class test {
public static void main(String[] args) {
//创建securityManager对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置securityManager对象的Realm对象
MyRealm myRealm = new MyRealm();
//shiro 使用密码加密器 为realm指定加密器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(1024);
myRealm.setCredentialsMatcher(matcher);
securityManager.setRealm(myRealm);
//3.SecurityUtils给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject 主体
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken admin = new UsernamePasswordToken("aaa", "123456");
//5.调用主体的登陆方法
subject.login(admin);
System.out.println("登陆成功");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("账号或者密码错误");
}
}
}
2.7 自定义授权
授权是在认证的基础上实现的
自定义授权 包括认证
public class MyRealm extends AuthorizingRealm {
UserService userService = new UserService();
//进行校验的时候调用的方法
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User)principalCollection.getPrimaryPrincipal();
//查询用户具有的权限
List<String> permission = userService.findPermissionByUsername(user.getUsername());
if(permission!=null && permission.size()>0){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permission);
return info;
}
return null;
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//根据token获取账号
String username = (String)authenticationToken.getPrincipal();
//根据账号查询
User user = userService.findByUsername(username);
if(user!=null){
//获取数据库中的账号、密码 、盐 、
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(), salt,this.getName());
return info;
}
return null;
}
}
public class UserService {
public User findByUsername(String username){
if("aaa".equals(username)){
return new User(1,"aaa","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}else if("bbb".equals(username)){
return new User(2,"bbb","e18be766ad36f52ea76b2b2c9ad04f8e","abc");
}
return null;
}
public List<String> findPermissionByUsername(String username){
List<String> list = new ArrayList<>();
if("aaa".equals(username)){
list.add("user:query");
list.add("user:add");
list.add("user:delete");
list.add("user:select");
}else if("bbb".equals(username)){
list.add("user:query");
list.add("user:add");
}
return list;
}
}
测试:
public class test {
public static void main(String[] args) {
//创建securityManager对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置securityManager对象的Realm对象
MyRealm myRealm = new MyRealm();
//为realm指定加密器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(1024);
myRealm.setCredentialsMatcher(matcher);
//设置securityManager的realm
securityManager.setRealm(myRealm);
//3.SecurityUtils给全局安全工具类设置安全管理器 设置上下文生效
SecurityUtils.setSecurityManager(securityManager);
//4.获取Subject 主体
Subject subject = SecurityUtils.getSubject();
try {
//UsernamePasswordToken:封装前端输入的账号和密码
UsernamePasswordToken admin = new UsernamePasswordToken("aaa", "123456");
//5.调用主体的登陆方法 比对shiro中realm中的账号和密码
subject.login(admin);
System.out.println("登陆成功");
System.out.println(subject.isPermitted("user:query"));
System.out.println(subject.isPermitted("user:add"));
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("账号或者密码错误");
}
}
}
2.8 加密
shiro它提供了很多中加密器,使用最多就是HashedCredentialsMatcher。它的底层使用的是Md5加密。
//密文---默认md5加密是不可逆的
//普通写法
Md5Hash md5Hash1 = new Md5Hash("123456");
System.out.println(md5Hash1);
//加盐
Md5Hash md5Hash2 = new Md5Hash("123456","abc");
System.out.println(md5Hash2);
//设置1024次加密
Md5Hash md5Hash3 = new Md5Hash("123456", "abc", 1024);
System.out.println(md5Hash3);
2.9 shiro整合ssm
使用步骤:
(1)创建一个maven项目(使用之前的ssm项目即可)
(2)ssm整合到web工程
①pom依赖
②spring配置文件
③web.xml配置文件
ssm整合笔记:
https://blog.csdn.net/weixin_44720982/article/details/125296120?spm=1001.2014.3001.5501
(3) 整合shiro
① 引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
② 修改spring配置文件
<!--整合shiro的配置内容-->
<!--①SecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm"/>
</bean>
<!--创建自定义realm类对象 配置类 上面笔记有这个类-->
<bean id="realm" class="com.ykq.realm.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--创建密码匹配器 使用SimpleCredentialsMatcher加密器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<property name="hashIterations" value="1024"/>
</bean>
<!--shiro过滤工厂: 设置过滤的规则-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--如果没有登录,跳转的路径 自己创建网页-->
<property name="loginUrl" value="/login.jsp"/>
<!--没有权限,跳转的路径 自己创建网页-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--shiro中内置很多过滤器,而每个过滤都有相应的别名.
这里的login 是控制层的登录功能路径
/login=anon 放行login
/**=anthc 是不放行其他路径
-->
<property name="filterChainDefinitions">
<value>
/login=anon
/**=authc
</value>
</property>
</bean>
注意: shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:
最常用的就是前两个
③ 修改web.xml文件
<!--shiro过滤器的代理-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
④在controller层中加入login
@PostMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
try {
subject.login(token);
return "redirect:/success.jsp";
}catch (Exception e){
return "redirect:/login.jsp";
}
}
⑤ 浏览器中测试即可
(4) ssm和shiro实现用户权限
一 、创建数据库(关于用户登录和用户权限)
user表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`userpwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', '4b05a8716a430f71c6911d2e9149092f', '男', '武汉', '07f63bbf285e4719bfee1cc2bb81db33');
INSERT INTO `user` VALUES (2, 'lisi', '99cc11855b41ff3e5f7c0af3c5090a8c', '女', '北京', '2d7e37f019144764ac02aa7220929f93');
INSERT INTO `user` VALUES (3, 'wangwu', '6d70c027801af4dc892ab340d4bf73b6', '女', '成都', '99f6bf1b540145699e6e5c90480105b0');
SET FOREIGN_KEY_CHECKS = 1;
user_role.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`userid` int(11) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`userid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (2, 2);
INSERT INTO `user_role` VALUES (3, 3);
SET FOREIGN_KEY_CHECKS = 1;
role.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`roleid` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`roleid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '仓管管理员');
INSERT INTO `role` VALUES (2, 'CEO');
INSERT INTO `role` VALUES (3, '保安');
SET FOREIGN_KEY_CHECKS = 1;
role_permission.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`perid` int(255) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`perid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (2, 2);
INSERT INTO `role_permission` VALUES (3, 1);
INSERT INTO `role_permission` VALUES (3, 2);
INSERT INTO `role_permission` VALUES (4, 1);
INSERT INTO `role_permission` VALUES (5, 3);
SET FOREIGN_KEY_CHECKS = 1;
permission.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`perid` int(11) NOT NULL AUTO_INCREMENT,
`pername` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`percode` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`perid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, '查询用户信息', '/user/query');
INSERT INTO `permission` VALUES (2, '修改用户', '/user/update');
INSERT INTO `permission` VALUES (3, '删除用户', '/user/delete');
INSERT INTO `permission` VALUES (4, '添加用户', '/user/insert');
INSERT INTO `permission` VALUES (5, '导出用户', '/user/export');
SET FOREIGN_KEY_CHECKS = 1;
(2)配置文件 spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.wx" />
<mvc:default-servlet-handler />
<mvc:annotation-driven />
<!-- 启动Shrio的注解 shiro加在controller应该属于springmvc的配置 -->
<!-- <bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>-->
<!--整合shiro的配置内容-->
<!-- securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm" />
</bean>
<!--创建自定义realm对象-->
<bean id="realm" class="com.wx.realm.MyRealm">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
<!--创建密码匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5" />
<property name="hashIterations" value="1024" />
</bean>
<!--shiro过滤工厂: 设置过滤的规则-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--如果没有登录,跳转的路径-->
<property name="loginUrl" value="/login.jsp"/>
<!--没有权限,跳转的路径-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<property name="filterChainDefinitions">
<value>
/login=anon
/**=authc
</value>
</property>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!--初始化连接数量:根据你项目的并发进行评估-->
<property name="initialSize" value="5"/>
<!--最大的连接个数-->
<property name="maxActive" value="10"/>
<!--等待时间 单位是毫秒-->
<property name="maxWait" value="3000"/>
</bean>
<!--整合mybatis-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--设置数据源-->
<property name="dataSource" ref="dataSource" />
<!--设置mybatis映射文件的路径-->
<property name="mapperLocations" value="classpath:mapper/*.xml" />
<!--配置分页-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
</bean>
</array>
</property>
</bean>
<!--为dao接口生成代理实现类-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wx.dao" />
</bean>
</beans>
(3)web.xml
<?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_4_0.xsd"
version="4.0">
<!--shiro过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--编码过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
(4)controller
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("login")
public Result login(String username, String userpwd){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,userpwd);
try {
subject.login(token);
return new Result(2000, "登录成功", null);
} catch (AuthenticationException e) {
e.printStackTrace();
return new Result(5000, "登录失败", null);
}
}
}
@RestController
public class PermissionController {
@GetMapping("/query")
public String query(){
return "user:query";
}
@GetMapping("/update")
public String update(){
return "user:update";
}/*@RequestMapping("/user")*/
@GetMapping("/delete")
public String delete(){
return "user:delete";
}
@GetMapping("/insert")
public String insert(){
return "user:insert";
}
@GetMapping("/export")
public String export(){
return "user:export";
}
}
(5)实体层
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt;
}
(6)service 及其实现类
public interface UserService {
List<String> findPermission(Integer roleid);
User selectByName(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
public List<String> findPermission(Integer roleid) {
return userDao.findPermission(roleid);
}
public User selectByName(String username) {
return userDao.selectByName(username);
}
}
(7)mapper层及映射文件
public interface UserDao {
List<String> findPermission(Integer userid);
User selectByName(String username);
}
<?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.wx.dao.UserDao">
<select id="findPermission" resultType="java.lang.String">
select percode from role_permission rp
join permission p on rp.perid=p.perid
join user_role ur on ur.roleid=rp.roleid
where ur.roleid=#{userid}
</select>
<select id="selectByName" resultType="com.wx.entity.User">
select * from user where username=#{username}
</select>
</mapper>
(8)realm (认证和授权)
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User)principalCollection.getPrimaryPrincipal();
List<String> permission = userService.findPermission(user.getUserid());
if(permission!=null && permission.size()>0){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permission);
return info;
}
return null;
}
/*认证*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//根据token获取账号
String username = (String)authenticationToken.getPrincipal();
//根据账号查询
User user = userService.selectByName(username);
if(user!=null){
//获取密码
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUserpwd(),salt,this.getName());
System.out.println(info);
return info;
}
return null;
}
}
(9)静态资源
unauthorized.jsp
<body>
权限不足,请联系管理员!
</body>
login.jsp
就是登录跳转
<el-form label-width="80px" :model="loginForm" :rules="rules" ref="loginFormRef" class="login_form">
<el-form-item label="账号:" prop="username">
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码:" prop="userpwd">
<el-input v-model="loginForm.userpwd"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm()">登录</el-button>
</el-form-item>
</el-form>
this.$refs['loginFormRef'].validate((valid) => {
if (valid) {
axios.post("login", qs.stringify(this.loginForm)).then(function (result) {
if (result.data.code === 2000) {
that.$message.success(result.data.msg);
location.href = "/main.jsp"
} else {
that.$message.error(result.data.msg);
}
});
}
})
进入主页后不同用户看到不同内容
main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
body{
text-align: center;
}
</style>
</head>
<body>
<h1>欢迎来到<shiro:principal property="username" />的主页</h1>
<h3>
<shiro:hasPermission name="/user/query">
<a href="/query"> 查询用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="/user/delete">
<a href="/delete"> 删除用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="/user/update">
<a href="/update"> 修改用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="/user/insert">
<a href="/insert"> 添加用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="/user/export">
<a href="/export"> 导出用户</a><br>
</shiro:hasPermission>
</h3>
</body>
</html>
使用hasPermission完成不同的用户登录看到自己的权限,但是这种状态是不安全的,如果我们直接输入http://localhost:8080/export
发现:
解决办法:
1. 拦截器---获取请求路径 然后根据你的路径判断当前用户是否具有该权限。
2. spring整合shiro时提供了一个注解:可以加载相应方法上。
<!-- 启动Shrio的注解 -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
使用注解
2.使用shiro注解
@RequiresPermissions(value = {"/user/query","/user/aaa"},logical= Logical.OR)
@RestController
public class PermissionController {
@GetMapping("/query")
@RequiresPermissions(value = {"/user/query","aaa"},logical = Logical.OR)
public String query(){
return "user:query";
}
@GetMapping("/update")
@RequiresPermissions(value = {"/user/update"})
public String update(){
return "user:update";
}/*@RequestMapping("/user")*/
@GetMapping("/delete")
@RequiresPermissions(value = {"/user/delete"})
public String delete(){
return "user:delete";
}
@GetMapping("/insert")
// @RequiresPermissions(value = {"/user/insert"})
public String insert(){
return "user:insert";
}
@GetMapping("/export")
@RequiresPermissions(value = {"/user/export"})
public String export(){
return "user:export";
}
}
此时访问不存在的权限会报错
可以使用全局异常处理:
@ControllerAdvice
public class MyException {
@ExceptionHandler(value = UnauthorizedException.class)
@ResponseBody
public Result auth(UnauthorizedException e){
e.printStackTrace();
return new Result(5002,"权限不足",null);
}
}
2.10 shiro整合ssm完成前后端分离
所谓前后端完全分离:后端响应的都是json数据,而不再是网页。
1. 登录成功或者失败应该返回json数据
2. 当未登录时返回json数据
3. 访问未授权的资源返回json数据
- 登录成功或者失败应该返回json数据
2. 当未登录时返回json数据 并拦截
public class LoginFilter extends FormAuthenticationFilter {
//未登录时候经过该方法
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result = new Result(5001, "请先登录", null);
ObjectMapper mapper = new ObjectMapper();
String s = mapper.writeValueAsString(result);
writer.print(s);
writer.flush();
writer.close();
return false;
}
}
注册过滤器:
<property name="filters">
<map>
<entry key="authc">
<bean class="com.wx.filter.LoginFilter" />
</entry>
</map>
</property>
- 访问未授权的资源返回json数据
2.11 springboot整合shiro完成前后端分离
前后端分离就是前端和后台代码分开写 这里仅仅是后台的代码 测试可以使用swagger2或者postman
这里使用的技术:springboot+mybaitis-plus+shiro+swagger2+mysql
(1)创建一个springboot项目
(2)导入依赖
<dependencies>
<!--引入shiro-->
<dependency>
<groupId>repMaven.org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<!--引入mp-->
<dependency>
<groupId>repMaven.com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<!--swagger图形化界面-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.7.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
(3)修改配置文件application.properities
配置数据源,数据库表是上述的那五张表
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
(4)创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userid;
private String username;
private String userpwd;
private String sex;
private String address;
private String salt;
}
(5)创建shiro配置类 之前ssm中的配置bean都是在spring.xml中配置的
但是springboot没有spring.xml所以需要创建相关的配置类 将之前的ssm中spring.xml中的bean在springboot中全部写成配置类
//配置类的标志
@Configuration
public class ShiroConfig {
//开始shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
/*自定义realm*/
@Bean
public Realm realm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(matcher());
return myRealm;
}
/*自定义加密器*/
@Bean
public CredentialsMatcher matcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1024);
matcher.setHashAlgorithmName("MD5");
return matcher;
}
//过滤器
@Bean(value = "shiroFilter")
public ShiroFilterFactoryBean factoryBean(){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager());
/*设置拦截规则*/
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("/login","anon");
hashMap.put("/*.html", "anon");
//放行Swagger2
hashMap.put("/swagger-ui.html","anon");
hashMap.put("/swagger/**","anon");
hashMap.put("/webjars/**", "anon");
hashMap.put("/swagger-resources/**","anon");
hashMap.put("/v2/**","anon");
hashMap.put("/static/**", "anon");
hashMap.put("/configuration/security", "anon");
hashMap.put("/configuration/ui", "anon");
hashMap.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(hashMap);
/*设置自定义认证过滤器*/
HashMap<String, Filter> filterMap = new HashMap<>();
filterMap.put("authc",new LoginFilter());
factoryBean.setFilters(filterMap);
return factoryBean;
}
/*注册filter 之前ssm中注册filter是在web.xml中*/
@Bean
public FilterRegistrationBean<Filter> filterFilter(){
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setName("shiroFilter");
filterRegistrationBean.setFilter(new DelegatingFilterProxy());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
(6)创建service dao entity
Controller层
@RestController
public class UserController {
@Autowired
UserService userService;
/*登录功能*/
@PostMapping("login")
@ApiOperation(value = "登录接口")
@ApiImplicitParams(
{
@ApiImplicitParam(value="账号",name="username",required = true),
@ApiImplicitParam(value="密码",name="userpwd",required = true)
}
)
public Result login(String username, String userpwd ){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,userpwd);
try {
subject.login(token);
return new Result(2000, "登录成功", null);
} catch (AuthenticationException e) {
e.printStackTrace();
return new Result(5000, "登录失败", null);
}
}
@PostMapping("query")
@ApiOperation(value = "查询接口")
@RequiresPermissions(value = {"/user/query","/user/aaa"},logical = Logical.OR)
public Result query(){
return new Result(2000,"user:query",null);
}
@PostMapping("update")
@ApiOperation(value = "修改接口")
@RequiresPermissions(value = {"/user/update"})
public Result update(){
return new Result(2000,"user:update",null);
}
@PostMapping("delete")
@ApiOperation(value = "删除接口")
@RequiresPermissions(value = {"/user/delete"})
public Result delete(){
return new Result(2000,"user:delete",null);
}
@PostMapping("insert")
@ApiOperation(value = "插入接口")
@RequiresPermissions(value = {"/user/insert"})
public Result insert(){
return new Result(2000,"user:insert",null);
}
@PostMapping("export")
@ApiOperation(value = "导出接口")
@RequiresPermissions(value = {"/user/export"})
public Result export(){
return new Result(2000,"user:export",null);
}
}
Result类 --用于返回固定的json格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private Integer code;
private String msg;
private Object data;
}
(7)创建swagger2注解
@Configuration
public class SwagerConfig {
@Bean
public Docket docket(){
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())//设置api文档信息
.select()
.apis(RequestHandlerSelectors.basePackage("com.wx.controller")) //指定为哪些包下的类生成接口文档。
.build();
return docket;
}
//定义自己接口文档信息
private ApiInfo apiInfo() {
Contact DEFAULT_CONTACT = new Contact("wx", "http://www.wx.com", "110@qq.com");
ApiInfo apiInfo = new ApiInfo("用户在线文档", "对用户的增删改查", "V1.0", "http://www.user.com",
DEFAULT_CONTACT, "XX科技", "http://www.xx.com", new ArrayList<VendorExtension>());
return apiInfo;
}
}
在主启动类中开启注解
(8)测试