使用Spring Security实现OAuth2授权认证
- 一、OAuth2.0基本知识
- 1、什么是 OAuth 2?
- 二、基于内存的用户、URL权限配置
- 1、配置pom文件添加依赖
- 2、配置授权服务器
- 3、配置 Security
- 4、添加测试接口
- 5、 开始测试
- 附:将令牌保存到 Redis 缓存服务器上
- 1、增加依赖
- 2、修改授权服务器配置
- 3、运行测试
- 三、基于数据库的用户配置
- 1、配置pom文件添加依赖、配置数据库
- 2、创建数据表(三张表用户表、角色表以及用户角色关联表)
- 3、创建实体类
- 4、创建数据库访问层
- 5、mapper.xml
- 6、创建 UserService
- 7、配置 Spring Security
- 8、运行测试
- 四、基于数据库的URL权限规则配置
- 1、数据库设计(两张表权限表,权限表和角色关联表)
- 2、创建实体类
- 3、创建数据库访问层
- 4、mapper.xml
- 5、自定义 FilterInvocationSecurityMetadataSource
- 6、自定义 AccessDecisionManager
- 7、配置 Spring Security
- 8、运行测试
一、OAuth2.0基本知识
1、什么是 OAuth 2?
网上关于OAuth2.0的介绍已经很多了,这里就不做过多的介绍,不太了解的朋友可自行百度
二、基于内存的用户、URL权限配置
1、配置pom文件添加依赖
<!-- Spring Security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--spring-security-oauth2 作为单点登录应用程序的依赖支持-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
2、配置授权服务器
1、下面是授权服务器配置代码。创建一个自定义类继承自 AuthorizationServerConfigurerAdapter,完成对授权服务器的配置,然后通过 @EnableAuthorizationServer 注解开启授权服务器:
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
// 该对象用来支持 password 模式
@Autowired
AuthenticationManager authenticationManager;
// 该对象用来将令牌信息存储到内存中
@Autowired(required = false)
TokenStore inMemoryTokenStore;
// 该对象将为刷新token提供支持
@Autowired
UserDetailsService userDetailsService;
// 指定密码的加密方式
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
// 配置 password 授权模式
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("password") //客户端ID
.authorizedGrantTypes("password", "refresh_token") //该client允许的授权模式为password和refresh_token两种
.accessTokenValiditySeconds(1800) // 配置access_token的过期时间
.resourceIds("rid") //配置资源id
.scopes("all")// 允许的授权范围
.secret("https://www.baidu.com/"); // client_secret
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
}
3、配置 Security
这里 Spring Security 的配置与传统的 Security 大体相同,不同在于:
1、这里多了两个 Bean,这两个 Bean 将注入授权服务器配置类中使用。
2、另外,这里的 HttpSecurity 配置主要是配置“oauth/**”模式的 URL,这一类的请求直接放行。
3、 注意:在这个 Spring Security 配置和资源服务器配置中,都涉及到了 HttpSecurity。其中 Spring Security 中的配置优先级高于资源服务器中的配置,即请求地址先经过 Spring Security 的 HttpSecurity,再经过资源服务器的 HttpSecurity。
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
//基于内存的用户角色信息配置
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
.roles("admin")
.and()
.withUser("sang")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
.roles("user");
}
// 基于内存的配置 URL 访问权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.and().csrf().disable();
}
}
4、添加测试接口
接着在 Conctoller 中添加如下三个接口用于测试,它们分别需要 admin 角色、use 角色以及登录后访问 。
@RestController
public class HelloController {
@GetMapping("/admin/hello")
public String admin() {
return "如果你看见这句话,说明你有ROLE_admin角色";
}
@GetMapping("/user/hello")
public String user() {
return "如果你看见这句话,说明你有ROLE_user角色";
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
5、 开始测试
(1)启动项目,首先通过 POST 请求获取 token:
1.请求地址:oauth/token
2.请求参数:用户名、密码、授权模式、客户端 id、scope、以及客户端密码
3.返回结果:access_token 表示获取其它资源是要用的令牌,refresh_token 用来刷新令牌,expires_in 表示 access_token 过期时间。
(2)当 access_token 过期后,可以使用 refresh_token 重新获取新的 access_token(前提是 access_token 未过期),这里也是 POST 请求:
1.请求地址:oauth/token(不变)
2.请求参数:授权模式(变成了 refresh_token)、refresh_token、客户端 id、以及客户端密码
3.返回结果:与获取前面登录获取 token 返回的内容项一样。不过每次请求,access_token 和 access_token有效期都会变化。(3)访问资源时,我们只需要携带上 access_token 参数即可:
附:将令牌保存到 Redis 缓存服务器上
在上面的样例中,令牌(Access Token)是存储在内存中,这种方式在单服务上可以体现出很好特效(即并发量不大,并且它在失败的时候不会进行备份)
我们也可以将令牌保存到数据库或者 Redis 缓存服务器上。使用这中方式,可以在多个服务之间实现令牌共享。下面我通过样例演示如何将令牌存储在 Redis 缓存服务器上,同时 Redis 具有过期等功能,很适合令牌的存储。
1、增加依赖
(1)首先编辑 pom.xml 文件,在前面的基础上增加 Redis 相关依赖:
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
(2)接着在 application.yml中配置 Redis 连接信息:
# 基本连接信息配置
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
# 连接池信息配置
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
2、修改授权服务器配置
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
// 该对象用来支持 password 模式
@Autowired
AuthenticationManager authenticationManager;
// 该对象用来将令牌信息存储到Redis中
@Autowired
RedisConnectionFactory redisConnectionFactory;
// // 该对象用来将令牌信息存储到内存中
// @Autowired(required = false)
// TokenStore inMemoryTokenStore;
// 该对象将为刷新token提供支持
@Autowired
UserDetailsService userDetailsService;
// 指定密码的加密方式
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
// 配置 password 授权模式
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("password") //客户端ID
.authorizedGrantTypes("password", "refresh_token") //授权模式为password和refresh_token两种
.accessTokenValiditySeconds(1800) // 配置access_token的过期时间
.resourceIds("rid") //配置资源id
.scopes("all")// 允许的授权范围
.secret("https://www.baidu.com/");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))//配置令牌存放在Redis中
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
}
3、运行测试
启动项目,再次通过 /oauth/token 接口获取令牌。然后查看下 Redis 中的数据,可以发现令牌确实已经保存在 Redis 上了:
三、基于数据库的用户配置
以上内容中,认证数据都是定义在内存里。而在真实项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证。
接下来使用mybatsi连接数据库
1、配置pom文件添加依赖、配置数据库
<!--mybatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>${mybatisplus-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generate</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 数据库驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
2、创建数据表(三张表用户表、角色表以及用户角色关联表)
注意:角色名需要有一个默认的前缀“ROLE_”
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`name` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`username` varchar(22) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 123457 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user` VALUES (123, '黄忠', '黄忠', '123');
INSERT INTO `user` VALUES (123456, '程咬金', '程咬金', '123');
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(12) NOT NULL,
`name` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`nameZh` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `role` VALUES (1, 'ROLE_admin', '射手');
INSERT INTO `role` VALUES (2, 'ROLE_user', '战士');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(12) NOT NULL,
`roleId` int(12) NULL DEFAULT NULL,
`userId` int(12) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user_role` VALUES (1, 1, 123456);
INSERT INTO `user_role` VALUES (2, 2, 123456);
INSERT INTO `user_role` VALUES (3, 2, 123);
3、创建实体类
@Data// 要使用 @Data 注解要先引入lombok
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String name;
private String username;
private String password;
@TableField(exist = false)
private List<Role> roles;
@Override
protected Serializable pkVal() {
return this.id;
}
}
@Data
public class Role extends Model<Role> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String name;
private String nameZh;
@Override
protected Serializable pkVal() {
return this.id;
}
}
4、创建数据库访问层
public interface UserMapper extends BaseMapper<User> {
User findOneByUsername(@Param("username") String username);
}
public interface RoleMapper extends BaseMapper<Role> {
List<Role> findRolesByUserId(@Param("userId") Integer integer);
}
5、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.bobo.mapper.UserMapper">
<!-- 开启二级缓存 -->
<!-- <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>-->
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.bobo.entity.User">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="username" property="username" />
<result column="password" property="password" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name,username, password
</sql>
<select id="findOneByUsername" resultMap="BaseResultMap" parameterType="java.lang.String">
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.bobo.mapper.RoleMapper">
<!-- 开启二级缓存 -->
<!-- <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/> -->
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.bobo.entity.Role">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="nameZh" property="nameZh" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, nameZh
</sql>
<select id="findRolesByUserId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
SELECT
r.*
FROM
`user` u
LEFT JOIN
user_role ur on u.id=ur.userId
LEFT JOIN
role r on r.id= ur.roleId
where
u.id=#{userId}
</select>
</mapper>
6、创建 UserService
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper usermapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = usermapper.findOneByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不对");
}
List<GrantedAuthority> auth =
AuthorityUtils.commaSeparatedStringToAuthorityList("role_shooter");
// 多角色
String sysRoles = new String("");
// 获取关联角色
List<Role> roles = roleMapper.findRolesByUserId(user.getId());
for (Role role : roles) {
sysRoles += role.getName() + ",";
}
if (sysRoles.length() > 0) {
sysRoles = (String) sysRoles.subSequence(0, sysRoles.length() - 1);
}
// 生成角色权限
auth = AuthorityUtils.commaSeparatedStringToAuthorityList(sysRoles);
UserDetails userDetails = new
org.springframework.security.core.userdetails.User(user.getUsername(),
new BCryptPasswordEncoder().encode(user.getPassword()), auth);
return userDetails;
}
}
7、配置 Spring Security
Spring Security 大部分配置与前文一样,只不过这次没有配置内存用户,而是将刚刚创建好的 UserService 配置到 AuthenticationManagerBuilder 中。
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService userService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//基于内存的用户角色信息配置
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .withUser("admin")
// .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
// .roles("admin")
// .and()
// .withUser("sang")
// .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
// .roles("user");
// }
// 基于数据库用户及其对应的角色
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
//配置 URL 访问权限
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.antMatcher("/oauth/**").authorizeRequests()
// .antMatchers("/oauth/**").permitAll()
// .and().csrf().disable();
// }
// 基于内存的配置 URL 访问权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 开启 HttpSecurity 配置
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated() //表示剩余的其他接口,登录之后就能访问
.and().formLogin()//formLogin() 方法表示开启表单登录
.loginProcessingUrl("/login")// 开启表单登录并配置登录接口
.permitAll()//permitAll() 表示和登录相关的接口都不需要认证即可访问
.and().csrf().disable(); // 关闭csrf
}
}
8、运行测试
接下来测试一下,我们使用 admin 用户进行登录,可以访问hello、/admin/hello,而/user/hello需要user,所以admin无法访问
四、基于数据库的URL权限规则配置
1、数据库设计(两张表权限表,权限表和角色关联表)
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` int(10) NOT NULL,
`permission` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `permission` VALUES (1, '/admin/**');
INSERT INTO `permission` VALUES (2, '/user/**');
DROP TABLE IF EXISTS `permission_role`;
CREATE TABLE `permission_role` (
`id` int(11) NOT NULL,
`permission_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `permission_role` VALUES (1, 1, 1);
INSERT INTO `permission_role` VALUES (2, 2, 2);
2、创建实体类
@Data
public class permission {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String permission;
@TableField(exist = false)
private List<Role> roles;
}
3、创建数据库访问层
@Mapper
public interface MenuMapper {
List<Menu> getAllMenus();
}
4、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.bobo.mapper.PermissionMapper">
<!-- 开启二级缓存 -->
<!-- <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>-->
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.bobo.entity.Permission">
<id property="id" column="id"/>
<result property="permission" column="permission"/>
<collection property="roles" ofType="com.bobo.entity.Role">
<id property="id" column="rid"/>
<result property="name" column="rname"/>
<result property="nameZh" column="rnameZh"/>
</collection>
</resultMap>
<select id="getAllMenus" resultMap="BaseResultMap">
SELECT p.*,r.id AS rid,r.name AS rname,r.nameZh AS rnameZh
FROM permission p LEFT JOIN permission_role pr ON p.`id`=pr.`mid` LEFT JOIN role r ON prs.`rid`=r.`id`
</select>
</mapper>
5、自定义 FilterInvocationSecurityMetadataSource
注意:自定义 FilterInvocationSecurityMetadataSource 主要实现该接口中的 getAttributes 方法,该方法用来确定一个请求需要哪些角色。
@Component
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {
// 创建一个AnipathMatcher,主要用来实现ant风格的URL匹配。
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
PermissionMapper permissionMapper;
@Override
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
// 从参数中提取出当前请求的URL
String requestUrl = ((FilterInvocation) object).getRequestUrl();
// 从数据库中获取所有的资源信息,即本案例中的permission表以及permission所对应的role
// 在真实项目环境中,开发者可以将资源信息缓存在Redis或者其他缓存数据库中。
List<Permission> allMenus = permissionMapper.getAllMenus();
// 遍历资源信息,遍历过程中获取当前请求的URL所需要的角色信息并返回。
for (Permission permission : allMenus) {
if (antPathMatcher.match(permission.getPermission(), requestUrl)) {
List<Role> roles = permission.getRoles();
String[] roleArr = new String[roles.size()];
for (int i = 0; i < roleArr.length; i++) {
roleArr[i] = roles.get(i).getName();
}
return SecurityConfig.createList(roleArr);
}
}
// 如果当前请求的URL在资源表中不存在相应的模式,就假设该请求登录后即可访问,即直接返回 ROLE_LOGIN.
return SecurityConfig.createList("ROLE_LOGIN");
}
// 该方法用来返回所有定义好的权限资源,Spring Security在启动时会校验相关配置是否正确。
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// 如果不需要校验,那么该方法直接返回null即可。
return null;
}
// supports方法返回类对象是否支持校验。
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
6、自定义 AccessDecisionManager
这里与前文的配置相比,主要是修改了 configure(HttpSecurity http) 方法的实现并添加了两个 Bean。至此我们边实现了动态权限配置,权限和资源的关系可以在 menu_role 表中动态调整。
@Component
public class UrlAccessDecisionManager
implements AccessDecisionManager {
// 该方法判断当前登录的用户是否具备当前请求URL所需要的角色信息
@Override
public void decide(Authentication auth,
Object object,
Collection<ConfigAttribute> ca){
Collection<? extends GrantedAuthority> auths = auth.getAuthorities();
// 如果具备权限,则不做任何事情即可
for (ConfigAttribute configAttribute : ca) {
// 如果需要的角色是ROLE_LOGIN,说明当前请求的URL用户登录后即可访问
// 如果auth是UsernamePasswordAuthenticationToken的实例,说明当前用户已登录,该方法到此结束
if ("ROLE_LOGIN".equals(configAttribute.getAttribute())
&& auth instanceof UsernamePasswordAuthenticationToken) {
return;
}
// 否则进入正常的判断流程
for (GrantedAuthority authority : auths) {
// 如果当前用户具备当前请求需要的角色,那么方法结束。
if (configAttribute.getAttribute().equals(authority.getAuthority())) {
return;
}
}
}
// 如果不具备权限,就抛出AccessDeniedException异常
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
7、配置 Spring Security
这里与前文的配置相比,主要是修改了 configure(HttpSecurity http) 方法的实现并添加了两个 Bean。至此我们边实现了动态权限配置,权限和资源的关系可以在 menu_role 表中动态调整。
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService userService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//基于内存的用户角色信息配置
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .withUser("admin")
// .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
// .roles("admin")
// .and()
// .withUser("sang")
// .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
// .roles("user");
// }
// 基于数据库用户及其对应的角色
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
//配置 URL 访问权限
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.antMatcher("/oauth/**").authorizeRequests()
// .antMatchers("/oauth/**").permitAll()
// .and().csrf().disable();
// }
// 基于内存的配置 URL 访问权限
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests() // 开启 HttpSecurity 配置
// .antMatchers("/login").permitAll() // admin/** 模式URL必须具备ADMIN角色
// .antMatchers("/admin/**").hasRole("admin")
// .antMatchers("/user/**").hasRole("user")
// .anyRequest().authenticated() // 用户访问其它URL都必须认证后访问(登录后访问)
// .and().formLogin()//formLogin() 方法表示开启表单登录
// .loginProcessingUrl("/login")// 开启表单登录并配置登录接口
// .permitAll()//permitAll() 表示和登录相关的接口都不需要认证即可访问
// .and().csrf().disable(); // 关闭csrf
// }
// 配置 数据库配置的URL 访问权限
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(cfisms());
object.setAccessDecisionManager(cadm());
return object;
}
})
.and().formLogin().loginProcessingUrl("/login").permitAll()//开启表单登录并配置登录接口
.and().csrf().disable(); // 关闭csrf
}
@Bean
CustomMetadataSource cfisms() {
return new CustomMetadataSource();
}
@Bean
UrlAccessDecisionManager cadm() {
return new UrlAccessDecisionManager();
}
8、运行测试