本文涉及知识点
- Spring Boot
- Spring Security
- OAuth 2.0
- MySQL
- MyBatis
- Redis
- JWT
使用Spring Boot 作为项目骨架,随后会出一篇使用Spring Cloud将这些技术点进行整合。前者更适合小型项目或练习用,后者更适合在中大型项目中部署使用。
该项目已开源到GitHub传送门。这篇文章会尽可能详细的讲解每一个步骤,若做不出来或有问题可以去GitHub下载本项目的源码或私信作者。
一、项目结构
config : 配置包,用于配置OAuth,Security, JWT
controller:外部可请求的资源
dao:数据持久层
entity:实体类
service:业务层
util:工具包
user_db:数据库文件
二、步骤讲解
- 创建项目并配置环境。
- 创建实体类,dao层,mapper文件,进行数据库连接。
- 配置Spring Security,包含实现UserDetailsService接口,配置密码编码器,设置安全拦截器和认证管理器。
- 配置JWT,进行Token设置,使用Jwt令牌存储方案。
- 配置OAuth 2.0 认证服务器,将jwt令牌存储在redis中,客户端目前使用Memory存储,真实项目需将其替换为jdbc存储。设置授权码模式下授权码从内存中获取,也可设置为jdbc模式。
- 配置OAuth 2.0 资源服务器,配置服务令牌解析服务器和资源访问安全配置。
三、代码实现
1. 创建项目并配置环境
可以在创建项目时进行勾选,可以直接复制pom文件中的坐标
版本:
- spring boot : 2.3.0
- spring-cloud-starter-oauth2 :2.2.1.RELEASE
- mybatis-spring-boot-starter : 2.1.2
- mysql : 5.1.47
使用 spring-cloud-starter-oauth2 可以省去很多引其他包的步骤,尽量避免由于版本造成的问题报错。
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>
<groupId>com.aaa</groupId>
<artifactId>springboot-security-oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-security-oauth2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
application.properties
在这里对web、DataSource、MyBatis、Redis进行配置
注意:pom.xml中对于jdbc驱动引用在spring.datasource.driver-class-name对应的值不同,一定要根据自己在pom文件选择的jdbc驱动版本选择driver-class-name。
# web
server.port=8080
server.servlet.context-path=/
spring.application.name=security-springboot
# DataSource
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
#logging.level.root=debug
# redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.timeout=0
2. 连接数据库
这里与通常使用mybatis连接数据库方式相同。不难但步骤较多
a. 创建数据库
user_db.sql
-- ----------------------------
-- Table structure for t_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限标识符',
`description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
`url` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES ('1', 'p1', '测试资源\r\n1', '/resource/r1');
INSERT INTO `t_permission` VALUES ('2', 'p2', '测试资源2', '/resource/r2');
INSERT INTO `t_permission` VALUES ('3', 'p3', '测试资源3', '/resource/r3');
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `unique_role_name`(`role_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES ('1', '管理员', NULL, NULL, NULL, '');
-- ----------------------------
-- Table structure for t_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission` (
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`permission_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`role_id`, `permission_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_role_permission
-- ----------------------------
INSERT INTO `t_role_permission` VALUES ('1', '1');
INSERT INTO `t_role_permission` VALUES ('1', '2');
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`fullname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户姓名',
`mobile` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'zhangsan', '$2a$10$37vdSYJUVguwXpLDnZfEt.UDC0y6Yk2RCzFuJKfOrWCiTnUFlmj3K', NULL, NULL);
INSERT INTO `t_user` VALUES (2, 'aaa', '$2a$10$UDvMFn8koZzJ70JGriqkbeVBELa.EFnFDFfkglZfiYhUyxryK3ebi', NULL, NULL);
INSERT INTO `t_user` VALUES (3, 'bbb', '$2a$10$eLA6kZcUI2PLvwX9n4unwe4hNZWlVBR5JuJ1fOQaHz9qnFxs.1jS.', NULL, NULL);
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`creator` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES ('1', '1', NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;
b. 创建实体类
User.java
package com.aaa.entity;
/**
* 用户信息
* @author 淮南King
*/
public class User {
/**
* 用户id
*/
private String id;
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 用户角色ID
*/
private Integer roleId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
}
Permission.java
package com.aaa.entity;
/**
* 权限信息
* @author 淮南King
*/
public class Permission {
/**
* 权限id
*/
private String id;
/**
* 权限代号
*/
private String code;
/**
* 权限描述
*/
private String description;
/**
* 路径
*/
private String url;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
c. 创建dao层
UserDao.java
package com.aaa.dao;
import com.aaa.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 用户信息持久层
* @author 淮南King
*/
@Mapper
public interface UserDao {
/**
* 根据账号查询用户信息
*
* @param username 用户姓名
* @return 用户信息
*/
User getUserByUsername(String username);
/**
* 根据用户id查询用户权限
*
* @param userId 用户id
* @return 权限列表
*/
List<String> findPermissionsByUserId(String userId);
}
d. 创建service层
UserService.java
package com.aaa.service;
import com.aaa.dao.UserDao;
import com.aaa.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 用户信息持久业务层
*
* @author 淮南King
* @date 2020-07-21
*/
@Service
public class UserService {
@Autowired UserDao dao;
public User getUserByUsername(String username) {
return dao.getUserByUsername(username);
}
public List<String> findPermissionsByUserId(String userId) {
return dao.findPermissionsByUserId(userId);
}
}
d. mapper层
UserMapper.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.aaa.dao.UserDao">
<select id="getUserByUsername" parameterType="String" resultType="com.aaa.entity.User">
select id,username,password,fullname,mobile from t_user where username = #{username}
</select>
<select id="findPermissionsByUserId" parameterType="String" resultType="String">
SELECT code FROM t_permission WHERE id IN(
SELECT permission_id FROM t_role_permission WHERE role_id IN(
SELECT role_id FROM t_user_role WHERE user_id = #{id} ))
</select>
</mapper>
3. 配置Spring Security
a. 配置UserDetailsService
UserDetail.java
package com.aaa.config;
import com.aaa.dao.UserDao;
import com.aaa.entity.User;
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;
/**
* 实现spring-security核心接口UserDetailsService
* 负载用户特定数据
*
* @author 淮南King
*/
@Service
public class UserDetail implements UserDetailsService {
@Autowired UserDao userDao;
/**
* 根据账号查询用户信息
* @param username
* @return
*/
@Override public UserDetails loadUserByUsername(String username) {
//将来连接数据库根据账号查询用户信息
User user = userDao.getUserByUsername(username);
//当查询此用户不存在时,将抛出用户名未找到异常
if (user == null) {
throw new UsernameNotFoundException("No such user found, the user name is: "+username);
}
//根据用户id查询权限
List<String> permissions = userDao.findPermissionsByUserId(user.getId());
//将permissions转为数组
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
//创建UserDetails 将从数据库查询到的用户信息包装返回给Security
UserDetails userDetails =
org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).authorities(permissionArray)
.build();
return userDetails;
}
}
b. security核心配置
WebSecurityConfig.java
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.password.PasswordEncoder;
/**
* spring security配置<br>
* Order -> 指定优先级别 值越低,优先级越高。值越高,优先级越低<br>
* EnableGlobalMethodSecurity 启用全局方法安全,启用pre注解
* @author 淮南King
*/
@Configuration
@Order(10)
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码编码器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 认证管理器
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 安全拦截机制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭跨域伪造请求拦截
http.csrf().disable();
//开启授权配置
http.authorizeRequests()
//允许访问授权接口
.antMatchers("/login/**","/oauth/**").permitAll()
//其他所有请求直接放行,权限验证在资源服务器中进行
.anyRequest().permitAll();
//允许表单登录
http.formLogin().permitAll();
}
}
4. 配置JWT令牌
TokenConfig.java
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* Token 配置类
* @author 淮南King
* @date 2020-08-06
*/
@Configuration
public class TokenConfig {
/**
* 签名密钥
*/
private String SIGNING_KEY = "secret";
/**
* token存储
* @return
*/
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
/**
* Jwt访问令牌转换器
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//对称秘钥,资源服务器使用该秘钥来验证
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
5. 配置认证服务器
MyAuthorizationServerConfig.java
package com.aaa.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import java.util.Arrays;
/**
* 认证服务器
*
* @author 淮南King
* @date 2020-08-04
*/
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired private RedisConnectionFactory redisConnectionFactory;
@Autowired private ClientDetailsService clientDetailsService;
@Autowired private AuthenticationManager authenticationManager;
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
/**
* 客户端详情服务
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用in-memory存储
clients.inMemory()
// client_id
.withClient("c1")
//客户端密钥
.secret(new BCryptPasswordEncoder().encode("secret"))
//资源列表
.resourceIds("res1")
// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// 允许的授权范围
.scopes("all")
//false跳转到授权页面
.autoApprove(false)
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
/**
* 设置token存储在redis中
* @return
*/
@Bean
public TokenStore redisTokenStore() {
//使用redis存储token
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
//设置redis token存储中的前缀
redisTokenStore.setPrefix("auth-token:");
return redisTokenStore;
}
/**
* 令牌管理服务
*
* @return
*/
@Bean
@Primary
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
//客户端详情服务
service.setClientDetailsService(clientDetailsService);
//支持刷新令牌
service.setSupportRefreshToken(true);
//令牌存储服务
service.setTokenStore(redisTokenStore());
//令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
// 令牌默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天
service.setRefreshTokenValiditySeconds(259200);
return service;
}
@Autowired private AuthorizationCodeServices authorizationCodeServices;
//授权码模式 需要配置
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
//设置授权码模式的授权码如何存取
return new InMemoryAuthorizationCodeServices();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
//令牌管理服务
.tokenServices(tokenService())
//配置JWT转换器
.accessTokenConverter(accessTokenConverter)
//认证管理器
.authenticationManager(authenticationManager)
//授权码服务
.authorizationCodeServices(authorizationCodeServices)
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
//oauth/token_key是公开
.tokenKeyAccess("permitAll()")
//oauth/check_token公开
.checkTokenAccess("permitAll()")
//表单认证(申请令牌)
.allowFormAuthenticationForClients();
}
}
6. 配置资源服务器
MyResourceServerConfig.java
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* 资源服务器
*
* @author 淮南King
* @date 2020-08-04
*/
@Configuration
@Order(2)
@EnableResourceServer
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
/**
* 资源服务令牌解析服务
* @return
*/
@Bean
public ResourceServerTokenServices resourceTokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(resourceTokenService())
.stateless(true);
}
/**
* 资源访问安全配置
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//关闭跨站请求防护
http.csrf().disable();
http
.authorizeRequests()
// '/oauth/token' 请求进行直接放行
.antMatchers("/oauth/token").permitAll()
// '/resource/**' 资源需要有all 范围
.antMatchers("/resource/**").access("#oauth2.hasScope('all')")
// 其他的资源进行放行
.anyRequest().permitAll();
//指定要使用的访问拒绝处理程序 OAuth2发送403
http.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
7. controller资源
AuthController.java
package com.aaa.controller;
import com.aaa.util.SecurityUtil;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 需要权限才能被访问的资源
* @author 淮南King
* @date 2020-07-21
*/
@RestController
@RequestMapping("/resource")
public class AuthController {
/**
* 测试资源1
* 拥有p1权限才可以访问
*
* @return
*/
@GetMapping("/r1")
@PreAuthorize("hasAuthority('p1')")
public String resource1() {
//获取当前线程的SecurityContext
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//获取当前线程的名称
return SecurityUtil.getUserNameByAuthentication(authentication) + " 访问资源1";
}
/**
* 测试资源2
* 拥有p2权限才可以访问
*
* @return
*/
@GetMapping("/r2")
@PreAuthorize("hasAuthority('p2')")
public String resource2() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return SecurityUtil.getUserNameByAuthentication(authentication) + " 访问资源2";
}
/**
* 测试资源3
* 拥有p3权限才可以访问
*
* @return
*/
@GetMapping("/r3")
@PreAuthorize("hasAuthority('p3')")
public String resource3() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return SecurityUtil.getUserNameByAuthentication(authentication) + " 访问资源3";
}
}
TestController.java
package com.aaa.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 淮南King
*/
@RestController
public class TestController {
@GetMapping("test")
public String test(){
return "test访问成功,这个不需要权限哦!";
}
}
四、测试
1. 密码模式获取token
http://localhost:8080/oauth/token? client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123
使用post请求后可以获取到token