CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO
`security_oauth_demo`.`user`(`id``username`,`password`, `salt`)
VALUES
(1, 'admin''$2a$10$WuZUcd3Uc1IQ8uNTuIPrvuoToxiZ/CNtfgQL/M/vFQu63pYYQExZK', '1');
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<druid.version>1.1.14druid.version>
<fastjson.version>1.2.68fastjson.version>
<security.oauth2.version>2.3.5.RELEASEsecurity.oauth2.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.security.oauthgroupId>
<artifactId>spring-security-oauth2artifactId>
<version>${security.oauth2.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-jwtartifactId>
<version>1.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.26version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("user")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String salt;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yc.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.yc.entity.UserInfo;
public interface UserService extends IService<UserInfo> {
String login(UserInfo user);
String add(UserInfo user);
String findOne(UserInfo user);
String update(UserInfo user);
}
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yc.entity.UserInfo;
import com.yc.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserInfo> implements UserService {
@Autowired
UserMapper userMapper;
/**
* 模拟登录
* @param user
* @return
*/
@Override
public String login(UserInfo user) {
UserInfo sysUser = userMapper.selectOne(
new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, user.getUsername()));
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "200");
jsonObject.put("msg", "登录成功");
return jsonObject.toString();
}
@Override
public String add(UserInfo user) {
int insert = userMapper.insert(user);
return insert +"";
}
@Override
public String findOne(UserInfo user) {
UserInfo sysUser = userMapper.selectOne(
new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, user.getUsername()));
return sysUser.getUsername();
}
@Override
public String update(UserInfo user) {
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("id", user.getId());
int update = userMapper.update(user, wrapper);
return "成功更新"+ update+ "条数据";
}
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yc.entity.UserInfo;
import com.yc.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
if (s == null || "".equals(s)) {
throw new RuntimeException("用户不能为空");
}
// 调用方法查询用户
UserInfo sysUser = userMapper.selectOne(
new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, s));
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_FRONT_USER" ));
return new User(sysUser.getUsername(),sysUser.getPassword(), authorities);
}
AuthorizationServer 需要继承AuthorizationServerConfigurerAdapter
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
}
AuthorizationServerConfigurerAdapter源码
ClientDetailsServiceConfigurer 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),
ClientDetailsService负责查找ClientDetails,而ClientDetails有几个重要的属性如下列表
// clients.withClientDetails(clientDetailsService);
clients.inMemory()// 使用in‐memory存储
.withClient("c1")// client_id
.secret(new BCryptPasswordEncoder().encode("secret"))
//资源服务id
.resourceIds("res1")
// 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("all")// 允许的授权范围 是一个标识
.autoApprove(false)//false代表如果是授权码模式 就跳转到授权的页面.
//加上验证回调地址
.redirectUris("http://www.baidu.com");
AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理,令牌可以被用来
加载身份信息,里面包含了这个令牌的相关权限。
自己可以创建 AuthorizationServerTokenServices 这个接口的实现,则需要继承 DefaultTokenServices 这个类,
里面包含了一些有用实现,你可以使用它来修改令牌的格式和令牌的存储。默认的,当它尝试创建一个令牌的时
候,是使用随机值来进行填充的,除了持久化令牌是委托一个 TokenStore 接口来实现以外,这个类几乎帮你做了
所有的事情。并且 TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore ,如其命名,所有的
令牌是被保存在了内存中。除了使用这个类以外,你还可以使用一些其他的预定义实现,下面有几个版本,它们都实现了TokenStore 接口:
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.InMemoryTokenStore;
/**
*
* 令牌存储策略
*/
@Configuration
public class TokenConfig {
@Bean
public TokenStore tokenStore() {
//内存模式
return new InMemoryTokenStore();
}
}
//令牌服务
@Autowired
private TokenStore tokenStore;
//客户端详情服务
@Autowired
private ClientDetailsService clientDetailsService;
/**
*
* 配置令牌服务
* 不管是什么模式都需要配置
* 需要使用到客户端clientDetailsService和令牌服务tokenStore
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
//客户端详情
service.setClientDetailsService(clientDetailsService);
//刷新token
service.setSupportRefreshToken(true);
//令牌服务
service.setTokenStore(tokenStore);
// 令牌默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天
service.setRefreshTokenValiditySeconds(259200);
return service;
}
AuthorizationServerEndpointsConfigurer 这个对象的实例可以完成令牌服务以及令牌endpoint配置。
配置授权类型(Grant Types)
AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型(Grant Types):
配置授权端点的URL(Endpoint URLs):
AuthorizationServerEndpointsConfigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链
接,它有两个参数:
@Bean
public AuthorizationCodeServices authorizationCodeServices() { //设置授权码模式的授权码如何
存取,暂时采用内存方式
return new InMemoryAuthorizationCodeServices();
}
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
//密码模式所需要的
.authenticationManager(authenticationManager)
//授权码模式所需要的
.authorizationCodeServices(authorizationCodeServices)
//如果你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现
//.userDetailsService(null)
//令牌管理服务
.tokenServices(tokenService())
//自定义令牌端点URL
// .pathMapping("/oauth/token","/user/token")
//自定义授权端点URL
// .pathMapping("/oauth/authorize","/user/authorize")
//允许POST提交
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
AuthorizationServerSecurityConfigurer用来配置令牌端点(Token Endpoint)的安全约束,在
AuthorizationServer中配置如下
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个endpoint完全公开。
.tokenKeyAccess("permitAll()")
//checkToken这个endpoint完全公开
.checkTokenAccess("permitAll()")
//允许表单认证
.allowFormAuthenticationForClients() ;
}
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()//除了/r/**,其它的请求可以访问
.and()
.formLogin()//允许表单登录
.loginPage("/login-view")//登录页面
.loginProcessingUrl("/login")
.successForwardUrl("/login-success")//自定义登录成功的页面地址
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login-view?logout");
}
/**
*
* oauth2.0 密码模式下需要认证管理器
* 密码模式所需要的
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
}
浏览器访问
http://localhost:8081/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
点击授权
得到授权码
客户端拿着授权码向授权服务器索要访问access_token
http://localhost:8081/oauth/token
参数
参数描述同授权码模式 ,注意response_type=token,说明是简化模式。
一般来说,简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法接收授权码。
http://localhost:8081/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
参数列表如下:
client_id:客户端准入标识。
client_secret:客户端秘钥。
grant_type:授权类型,填写password表示密码模式
username:资源拥有者用户名。
password:资源拥有者密码
这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我
们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。
参数列表如下
client_id:客户端准入标识。
client_secret:客户端秘钥。
grant_type:授权类型,填写client_credentials表示客户端模式
这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因
此这种模式一般用来提供给我们完全信任的服务器端服务。比如,合作方系统对接,拉取一组用户信息。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<druid.version>1.1.14druid.version>
<fastjson.version>1.2.68fastjson.version>
<security.oauth2.version>2.3.5.RELEASEsecurity.oauth2.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.security.oauthgroupId>
<artifactId>spring-security-oauth2artifactId>
<version>${security.oauth2.version}version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-jwtartifactId>
<version>1.1.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.26version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
@EnableResourceServer 注解到一个 @Configuration 配置类上,并且必须使用 ResourceServerConfigurer 这个
配置对象来进行配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个
对象的实例),下面是一些可以配置的属性:
ResourceServerSecurityConfigurer中主要包括:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends
ResourceServerConfigurerAdapter {
//授权服务器配置的资源id 保持一致
public static final String RESOURCE_ID = "res1";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenService())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//授权服务器配置的允许的授权范围 是一个标识 保持一致
.antMatchers("/**").access("#oauth2.hasScope('all')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
//资源服务令牌解析服务
@Bean
public ResourceServerTokenServices tokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
//授权服务器地址认证token的
/**
* 在这里面配置的security //tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个endpoint完全公开。
.tokenKeyAccess("permitAll()")
//checkToken这个endpoint完全公开
.checkTokenAccess("permitAll()")
//允许表单认证
.allowFormAuthenticationForClients() ;
**/
service.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
//客户端id
service.setClientId("c1");
//
service.setClientSecret("secret");
return service;
}
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").authenticated()//所有/user/**的请求必须认证通过
//拦截其它的请求可以访问
.anyRequest().permitAll();
}
}
@RestController
@RequestMapping(value = "/user")
public class UserController {
@GetMapping(value = "/test")
public String r1(){
return "访问资源1";
}
}
@Configuration
public class TokenConfig {
// @Bean
// public TokenStore tokenStore() {
// //内存模式
// return new InMemoryTokenStore();
// }
/**
*
*/
private String SIGNING_KEY = "TOKEN_CAT";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
}
**更改AuthorizationServer中的AuthorizationServerTokenServices **
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
//客户端详情
service.setClientDetailsService(clientDetailsService);
//刷新token
service.setSupportRefreshToken(true);
//令牌服务
service.setTokenStore(tokenStore);
新添加的代码
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
新添加的代码
// 令牌默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天
service.setRefreshTokenValiditySeconds(259200);
return service;
}
使用密码模式测试生成令牌
资源服务需要和授权服务拥有一致的签字、令牌服务等
@Configuration
public class TokenConfig {
// @Bean
// public TokenStore tokenStore() {
// //内存模式
// return new InMemoryTokenStore();
// }
/**
*
*/
private String SIGNING_KEY = "TOKEN_CAT";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
@Autowired
TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
// .tokenServices(tokenService())
//使用自己配置的令牌服务
.tokenStore( tokenStore)
.stateless(true);
}
截止目前客户端信息和授权码仍然存储在内存中,生产环境中通过会存储在数据库中,下边完善环境的配置
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '客户端标\r\n识',
`resource_ids` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '接入资源列表',
`client_secret` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '客户端秘钥',
`scope` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`access_token_validity` int DEFAULT NULL,
`refresh_token_validity` int DEFAULT NULL,
`additional_information` longtext CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`archived` tinyint DEFAULT NULL,
`trusted` tinyint DEFAULT NULL,
`autoapprove` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='接入客户端信息';
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` blob NULL,
INDEX `code_index`(`code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
插入数据
INSERT INTO `security_oauth_demo`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `create_time`, `archived`, `trusted`, `autoapprove`) VALUES ('c1', 'res1', '$2a$10$3CCcmHbkYEz55brA3BL6J.B7VW4HxsnkYmJ/Yz95f0Gvkpnammpoi', 'ROLE_ADMIN,ROLE_USER,ROLE_API', 'client_credentials,password,authorization_code,implicit,refresh_token', 'http://www.baidu.com', NULL, 7200, 259200, NULL, '2023-10-19 09:51:20', 0, 0, 'false');
INSERT INTO `security_oauth_demo`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `create_time`, `archived`, `trusted`, `autoapprove`) VALUES ('c2', 'res1', '$2a$10$3CCcmHbkYEz55brA3BL6J.B7VW4HxsnkYmJ/Yz95f0Gvkpnammpoi', 'ROLE_ADMIN,ROLE_USER,ROLE_API', 'client_credentials,password,authorization_code,implicit,refresh_token', 'http://www.baidu.com', NULL, 7200, 259200, NULL, '2023-10-19 09:51:20', 0, 0, 'false');
修改AuthorizationServer:
ClientDetailsService和AuthorizationCodeServices从数据库读取数据。
/**
* 1.客户端详情相关配置
*/
注入ClientDetailsService
/**
* 客户端认证服务 因为之前使用的是内存模式 直接配置信息就好了
* 现在使用的是数据库 所以要配置数据库信息
* @param dataSource
* @return
*/
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder( new BCryptPasswordEncoder() );
return clientDetailsService;
}
客户端配置使用刚刚的数据库模式 clientDetailsService
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
// clients.inMemory()// 使用in‐memory存储
// .withClient("c1")// client_id
// .secret(new BCryptPasswordEncoder().encode("secret"))
// //资源服务id
// .resourceIds("res1")
// // 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
// .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// .scopes("all")// 允许的授权范围 是一个标识
// .autoApprove(false)//false代表如果是授权码模式 就跳转到授权的页面.
// //加上验证回调地址
// .redirectUris("http://www.baidu.com");
}
配置授权码模式也使用数据库
原来的注释掉
// @Bean
// public AuthorizationCodeServices authorizationCodeServices() {
// //设置授权码模式的授权码如何 存取,暂时采用内存方式
// return new InMemoryAuthorizationCodeServices();
// }
//使用DataSource 模式
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
}
其中oauth_client_details表中client_secret客户端密钥字段 是secret使用new BCryptPasswordEncoder() 加密模式进行加密的,如果不能使用自行进行加密
http://localhost:8081/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
获取异常,错误信息如下
Resolved [error=“invalid_scope”, error_description=“Invalid scope: all”, scope=“ROLE_ADMIN ROLE_USER ROLE_API”]
意思是scope传入的是all,而数据库是这些ROLE_ADMIN ROLE_USER ROLE_API
更改scope为其中一个再次进行测试
http://localhost:8081/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_API&redirect_uri=http://www.baidu.com
在授权服务器添加资源服务器ResouceServerConfig
import com.yc.config.handler.CustomAccessDeniedHandler;
import com.yc.config.handler.CustomAuthExceptionEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.token.TokenStore;
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends
ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Autowired
TokenStore tokenStore;
/**
* 自定义访问无权限资源时的异常
*/
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
/**
* 自定义认证失败的异常
*/
@Autowired
private CustomAuthExceptionEntryPoint exceptionEntryPoint;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
//必须拥有RESOURCE_ID 配置, 如果不使用克注释掉
.resourceId(RESOURCE_ID)
//使用自己配置的令牌服务
.tokenStore( tokenStore)
// 自定义认证失败的异常 自定义访问无权限资源时的异常
.authenticationEntryPoint(exceptionEntryPoint).accessDeniedHandler(accessDeniedHandler)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
/**
* 拦截所有请求,同时scopes必须使用all的 如果不需要可以使用下面的注释掉,或者使用下面的认证配置
* .antMatchers("/**").access("#oauth2.hasScope('all')")
*/
http
.authorizeRequests()
.antMatchers("/**")
.access("#oauth2.hasScope('all')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
/**
*
*/
// 所有请求必须认证通过
// http.authorizeRequests()
// // 过滤放行
// .antMatchers().permitAll()
// .anyRequest().authenticated();
}
}
CustomAccessDeniedHandler
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义访问无权限资源时的异常
*
* @author
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler
{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException
{
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
System.out.println("权限不足,请联系管理员");
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print("权限不足,请联系管理员");
}
}
CustomAuthExceptionEntryPoint
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 自定义认证失败的异常
*
* @author
*/
@Component
public class CustomAuthExceptionEntryPoint implements AuthenticationEntryPoint
{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException
{
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
System.out.println("令牌不合法,禁止访问");
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print("令牌不合法,禁止访问");
}
}