文章目录
一、前期准备
1. 数据库脚本
USE `security_dy`;
/*Table structure for table `menu` */
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`pattern` VARCHAR(128) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `menu` */
INSERT INTO `menu`(`id`,`pattern`) VALUES
(1,'/db/**'),
(2,'/admin/**'),
(3,'/user/**');
/*Table structure for table `menu_role` */
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`mid` INT(11) DEFAULT NULL,
`rid` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `menu_role` */
INSERT INTO `menu_role`(`id`,`mid`,`rid`) VALUES
(1,1,1),
(2,2,2),
(3,3,3);
/*Table structure for table `role` */
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) DEFAULT NULL,
`nameZh` VARCHAR(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `role` */
INSERT INTO `role`(`id`,`name`,`nameZh`) VALUES
(1,'ROLE_dba','数据库管理员'),
(2,'ROLE_admin','系统管理员'),
(3,'ROLE_user','用户');
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) DEFAULT NULL,
`password` VARCHAR(255) DEFAULT NULL,
`enabled` TINYINT(1) DEFAULT NULL,
`locked` TINYINT(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `user` */
INSERT INTO `user`(`id`,`username`,`password`,`enabled`,`locked`) VALUES
(1,'root','$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq',1,0),
(2,'admin','$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq',1,0),
(3,'sang','$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq',1,0);
/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`uid` INT(11) DEFAULT NULL,
`rid` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*Data for the table `user_role` */
INSERT INTO `user_role`(`id`,`uid`,`rid`) VALUES
(1,1,1),
(2,1,2),
(3,2,2),
(4,3,3);
创建成功:
这里注意,创建角色时已经为其加上了ROLE_
,所以下面创建实体类时就不用添加了.
menu 表:存储角色对应的权限
2. 创建 SpringBoot 项目加入依赖
锁定mysql 的版本号,同时加入druid 连接池
配置 application.properties
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.url=jdbc:mysql://localhost:3306/security_dy
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
二、定义 Service
1. UserService
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("用户不存在!");
}
user.setRoles(userMapper.getRolesById(user.getId()));
return user;
}
}
2. MenuService
@Service
public class MenuService {
@Autowired
MenuMapper menuMapper;
public List<Menu> getAllMenus() {
return menuMapper.getAllMenus();
}
}
三、定义 Mapper
由于这里定义的 Mapper 比较多,所以直接把扫描路径加到启动类里:
@MapperScan(basePackages = "org.yolo.securitydy.mapper")
在 pom 中文件配置资源扫描路径:
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource><directory>src/main/resources</directory></resource>
</resources>
1. 定义 UserMapper 和 UserMapper.xml
public interface UserMapper {
User loadUserByUsername(String s);
List<Role> getRolesById(Integer id);
}
<?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="org.yolo.securitydy.mapper.UserMapper">
<select id="loadUserByUsername" resultType="org.yolo.securitydy.bean.User">
select * from user where username=#{
username}
</select>
<select id="getRolesById" resultType="org.yolo.securitydy.bean.Role">
select * from role where id in(select rid from user_role where uid=#{
id})
</select>
</mapper>
2. 定义 MenuMapper 和 MenuMapper.xml
public interface MenuMapper {
List<Menu> getAllMenus();
}
<?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="org.yolo.securitydy.mapper.MenuMapper">
<resultMap id="BaseResultMap" type="org.yolo.securitydy.bean.Menu">
<id property="id" column="id"/>
<result property="pattern" column="pattern"/>
<collection property="roles" ofType="org.yolo.securitydy.bean.Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<select id="getAllMenus" resultMap="BaseResultMap">
select m.*,r.`id` as rid,r.`name` as rname,r.`nameZh` as rnameZh from menu m left join menu_role mr on m.`id`=mr.`mid`
left join role r on mr.`rid`=r.`id`
</select>
</mapper>
三、定义 Config
现在想要访问哪个资源就具备那个角色,但是现在资源路径在数据库里:每个角色对应着不同的访问资源路径
1. MyFilter
定义一个过滤器,主要用于分析出请求地址
@Component
public class MyFilter implements FilterInvocationSecurityMetadataSource {
/**
* 路径匹配符
*/
AntPathMatcher pathMatcher = new AntPathMatcher();
@Autowired
MenuService menuService;
/**
* 这个集合返回的是需要的角色
* @param o
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//获得请求的地址
String requestUrl = ((FilterInvocation) o).getRequestUrl();
//获取数据库中的角色对应的资源路径信息(因为每次都要获取,所以可以结合 redis 将其存储到缓存中)
List<Menu> allMenus = menuService.getAllMenus();
//将请求地址信息和数据库中的资源路径信息进行对比
for (Menu menu : allMenus) {
//匹配成功,获取需要的角色
if (pathMatcher.match(menu.getPattern(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] rolesStr = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
rolesStr[i] = roles.get(i).getName();
}
return org.springframework.security.access.SecurityConfig.createList(rolesStr);
}
}
//默认的返回值,相当于一个特殊的标记符,拿到这个标记符单独做处理即可
return org.springframework.security.access.SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
2. MyAccessDecisionManager
主要实现,拿认证的信息和访问资源路径需要的信息进行比对,看是否符合需要的信息要求
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication:保存了当前登录用户的认证信息,有哪些角色
* @param o:获取当前请求对象
* @param collection:MyFilter 的返回值,访问资源路径需要哪些角色
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//需要的角色
for (ConfigAttribute attribute : collection) {
//对设置的默认值做处理
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 configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
3. SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Autowired
MyFilter myFilter;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
/**
* 将 MyFilter 和 MyAccessDecisionManager 配置加入
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(myAccessDecisionManager);
o.setSecurityMetadataSource(myFilter);
return o;
}
})
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
四、定义 Controller
@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";
}
}
对于 hello 接口,并没有存储路径资源,所以都可以访问