前言:在security基于数据库的认证(二)时,我们懂得了security是怎么从db里获取用户名和密码并且验证的,不过那些些权限还是静态配置的,本章主要讲如何实现动态配置权限
1.要准备的sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '资源路径',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '/db/**');
INSERT INTO `menu` VALUES (2, '/damin/**');
INSERT INTO `menu` VALUES (3, '/user/**');
-- ----------------------------
-- Table structure for menu_role_ref
-- ----------------------------
DROP TABLE IF EXISTS `menu_role_ref`;
CREATE TABLE `menu_role_ref` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`menu_id` int(11) NULL DEFAULT NULL COMMENT '菜单id',
`role_id` int(11) NULL DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of menu_role_ref
-- ----------------------------
INSERT INTO `menu_role_ref` VALUES (1, 1, 1);
INSERT INTO `menu_role_ref` VALUES (2, 2, 2);
INSERT INTO `menu_role_ref` VALUES (3, 3, 3);
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`description` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_db', '数据库管理员');
INSERT INTO `role` VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'ROLE_user', '用户');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NULL DEFAULT NULL,
`locked` tinyint(1) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$O8G0X/sUPAA76MV7U3BwY.3Uo8/QMBcqK678Rwkoz.fowbce.CLtO', 1, 0);
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$O8G0X/sUPAA76MV7U3BwY.3Uo8/QMBcqK678Rwkoz.fowbce.CLtO', 1, 0);
INSERT INTO `user` VALUES (3, 'tom', '$2a$10$O8G0X/sUPAA76MV7U3BwY.3Uo8/QMBcqK678Rwkoz.fowbce.CLtO', 1, 0);
-- ----------------------------
-- Table structure for user_role_ref
-- ----------------------------
DROP TABLE IF EXISTS `user_role_ref`;
CREATE TABLE `user_role_ref` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role_ref
-- ----------------------------
INSERT INTO `user_role_ref` VALUES (1, 1, 1);
INSERT INTO `user_role_ref` VALUES (2, 1, 2);
INSERT INTO `user_role_ref` VALUES (3, 2, 2);
INSERT INTO `user_role_ref` VALUES (4, 3, 3);
SET FOREIGN_KEY_CHECKS = 1;
2.pom.xml
<?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.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yl</groupId>
<artifactId>security-dy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security-dy</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**
3.application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
4.实体类
package com.yl.securitydy.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean locked;
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> list = new ArrayList<>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority(role.getName()));
}
return list;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", enabled=" + enabled +
", locked=" + locked +
'}';
}
}
package com.yl.securitydy.domain;
import java.io.Serializable;
public class Role implements Serializable {
private Integer id;
private String name;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}
package com.yl.securitydy.domain;
import java.io.Serializable;
import java.util.List;
public class Menu implements Serializable {
private Integer id;
private String url;
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "Menu{" +
"id=" + id +
", url='" + url + '\'' +
", roles=" + roles +
'}';
}
}
5.mapper
package com.yl.securitydy.mapper;
import com.yl.securitydy.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User loadUserByUserName(String username);
}
package com.yl.securitydy.mapper;
import com.yl.securitydy.domain.Role;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface RoleMapper {
List<Role> getRoleListByUserId(Integer userId);
}
package com.yl.securitydy.mapper;
import com.yl.securitydy.domain.Menu;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MenuMapper {
List<Menu> getAllMenuList();
}
6.mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yl.securitydy.mapper.UserMapper">
<select id="loadUserByUserName" resultType="com.yl.securitydy.domain.User">
select * from user where username = #{username}
</select>
</mapper>
<?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.yl.securitydy.mapper.RoleMapper">
<select id="getRoleListByUserId" resultType="com.yl.securitydy.domain.Role">
select * from role where id in (select role_id from user_role_ref where user_id = #{userId})
</select>
</mapper>
<?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.yl.securitydy.mapper.MenuMapper">
<resultMap id="baseResultMap" type="com.yl.securitydy.domain.Menu">
<id property="id" column="id"/>
<result property="url" column="url"/>
<collection property="roles" ofType="com.yl.securitydy.domain.Role">
<id property="id" column="rid"/>
<result property="name" column="name"/>
<result property="description" column="description"/>
</collection>
</resultMap>
<select id="getAllMenuList" resultMap="baseResultMap">
select m.*,r.id rid,r.name,r.description from menu m left join menu_role_ref mrr on m.id = mrr.menu_id left join role r on r.id = mrr.role_id;
</select>
</mapper>
7.service
package com.yl.securitydy.service;
import com.yl.securitydy.domain.Role;
import com.yl.securitydy.domain.User;
import com.yl.securitydy.mapper.RoleMapper;
import com.yl.securitydy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
List<Role> roles = roleMapper.getRoleListByUserId(user.getId());
user.setRoles(roles);
return user;
}
}
package com.yl.securitydy.service;
import com.yl.securitydy.domain.Menu;
import com.yl.securitydy.mapper.MenuMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MenuService {
@Autowired
MenuMapper menuMapper;
public List<Menu> getAllMenuList(){
return menuMapper.getAllMenuList();
}
}
8.自定义源数据拦截器
package com.yl.securitydy.config;
import com.yl.securitydy.domain.Menu;
import com.yl.securitydy.domain.Role;
import com.yl.securitydy.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
@Component
public class MyFilter implements FilterInvocationSecurityMetadataSource {
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
MenuService menuService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> menuList = menuService.getAllMenuList();
for (Menu menu : menuList) {
if (antPathMatcher.matchStart(menu.getUrl(),requestUrl)) {
List<Role> roles = menu.getRoles();
String[] roleStrs = new String[roles.size()];
for (int i = 0;i < roles.size();i++) {
roleStrs[i] = roles.get(i).getName();
}
return SecurityConfig.createList(roleStrs);
}
}
return SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
9.自定义决策管理器
package com.yl.securitydy.config;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class MyAccess implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute attribute : configAttributes) {
if ("ROLE_login".equals(attribute.getAttribute())) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("非法请求!");
} else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(attribute.getAttribute())) {
return;
}
}
}
throw new AccessDeniedException("非法请求!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
10.securityconfig
package com.yl.securitydy.config;
import com.yl.securitydy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Autowired
MyFilter myFilter;
@Autowired
MyAccess myAccess;
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(myAccess);
object.setSecurityMetadataSource(myFilter);
return object;
}
})
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
11.controller
package com.yl.securitydy.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
@GetMapping("/db/hello")
public String db() {
return "hello db";
}
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
}
12.测试
1.登录root账号(其拥有两个角色,可以访问/db/hello和/admin/hello)
2.root账号访问不了/user/hello接口
|