<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.http.SessionCreationPolicy;
/**
* 开启Spring方法级的安全保护
*/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权码模式在浏览器地址栏发起请求来获取 code
* .anyRequest().authenticated() 必须对该请求进行认证拦截,发现用户没有登陆的时候会弹出登陆框, 从而让用户输入用户名和密码进行登陆, 若是对该请求进行放行, 则登陆页无法弹出, 并抛出 InsufficientAuthenticationException
* .httpBasic() 因为用户未登陆访问了受保护的资源, 所以还要开启 httpBasic 进行简单认证, 否则会抛出 AccessDeniedException 异常,
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//关闭跨域保护
.authorizeRequests()
.antMatchers("/captcha/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//永远不会创建HttpSession, 默认配置
.and()
.headers().cacheControl().disable()//禁用缓存
;
}
/**
* 注入一个认证管理器, 自身不实现身份验证, 而是逐一向认证提供者进行认证, 直到某一个认证提供者能够成功验证当前用户的身份
*
* AuthenticationManager(认证管理器接口) 的默认实现类 ProviderManager, 管理多个 AuthenticationProvider(认证提供者)
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
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
*/
@Configuration
public class TokenConfig {
@Bean
public TokenStore jwtTokenStore() {
//令牌存储方案采用JWT
return new JwtTokenStore(jwtAccessTokenConverter());
}
/*
* AccessToken转换器: 定义 token 的生成方式
* JwtAccessTokenConverter: 表示采用 JWT 来生成
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(OauthConstant.OAUTH_SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
}
-- oauth2.0 默认有一套客户端表结构可进行替换,以下为自定义,其他用户的表结构就自行定义吧
CREATE TABLE `sys_client` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`client_id` varchar(32) NOT NULL COMMENT '客户端ID',
`resource_ids` varchar(256) DEFAULT NULL COMMENT '客户端密钥',
`client_secret` varchar(256) DEFAULT NULL COMMENT '资源ID列表',
`scope` varchar(256) DEFAULT NULL COMMENT '作用域',
`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '授权方式',
`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '回调地址',
`authorities` varchar(256) DEFAULT NULL COMMENT '权限列表',
`access_token_validity` int DEFAULT NULL COMMENT '令牌有效时间',
`refresh_token_validity` int DEFAULT NULL COMMENT '刷新令牌有效时间',
`additional_information` varchar(4096) DEFAULT NULL COMMENT '扩展信息',
`autoapprove` varchar(256) DEFAULT NULL COMMENT '是否自动放行',
`deleted` char(1) DEFAULT '0' COMMENT '删除标记,1:已删除,0:正常',
`platform_id` int NOT NULL DEFAULT '0' COMMENT '所属平台',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户端信息表';
-- 插入两条测试客户端
INSERT INTO `platform`.`sys_client`(`id`, `client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`, `deleted`, `platform_id`) VALUES (1, 'c1', '', '$2a$10$1Bg3qCxNVXobR2SJG9t0zOV45glOCH1MpvvPJDdyXCycWu/rZ1DOa', 'all', 'refresh_token,authorization_code,client_credentials,implicit', 'http://www.baidu.com', NULL, 72000, 259200, NULL, 'false', '0', 0);
INSERT INTO `platform`.`sys_client`(`id`, `client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`, `deleted`, `platform_id`) VALUES (2, 'c2', 'mo-wen-res', '$2a$10$1Bg3qCxNVXobR2SJG9t0zOV45glOCH1MpvvPJDdyXCycWu/rZ1DOa', 'all', 'password,refresh_token,captcha,authorization_code', 'http://www.baidu.com', NULL, 72000, 259200, NULL, 'false', '0', 0);
-- 查询客户端信息
String OAUTH_SQL_CLIENT = "SELECT client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove\n" +
"FROM sys_client WHERE client_id = ? AND deleted = 0";
-- 查询用户信息
String OAUTH_SQL_LOGIN_USER = "select user_id as id, `name`, phone, password from sys_user where phone = ?";
-- 查询用户权限
String OAUTH_SQL_USER_PERMISSION = "SELECT `name` FROM sys_permission WHERE id IN(\n" +
" SELECT permission_id FROM sys_role_permission WHERE role_id IN(\n" +
" SELECT role_id FROM sys_user_role WHERE user_id = ?\n" +
" )\n" +
")";
@Slf4j
@Service
@AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
// 查询用户信息的service, 自行定义
private final UserService userService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LoginUser loginUser = userService.getUserByPhone(s);
List<String> authorities = userService.getPermissionsByUserId(loginUser.getId());
log.info("当前登陆用户: [{}] 权限: [{}]", loginUser, authorities);
return User.withUsername(toJSONString(loginUser))
.password(loginUser.getPassword())
.authorities(authorities.toArray(new String[0]))
.build();
}
}
@Slf4j
@Service
public class JdbcClientDetailsServiceImpl extends JdbcClientDetailsService {
public JdbcClientDetailsServiceImpl(DataSource dataSource) {
super(dataSource);
}
@Resource
private PasswordEncoder bCryptPasswordEncoder;
@Override
public ClientDetails loadClientByClientId(String clientId) throws InvalidClientException {
super.setSelectClientDetailsSql(OauthConstant.OAUTH_SQL_CLIENT);
super.setPasswordEncoder(bCryptPasswordEncoder);
ClientDetails clientDetails = super.loadClientByClientId(clientId);
log.info("加载客户端信息: [{}], [{}]", clientId, clientDetails);
return clientDetails;
}
//用于密码的加密方式
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final JdbcClientDetailsServiceImpl clientDetailsServiceImpl;
private final UserDetailsServiceImpl userDetailsServiceImpl;
private final TokenStore jwtTokenStore;
private final JwtAccessTokenConverter jwtAccessTokenConverter;
private final AuthenticationManager authenticationManager;
private final PasswordEncoder bCryptPasswordEncoder;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomWebResponseExceptionTranslator customWebResponseExceptionTranslator;
/**
* 用来配置令牌端点的安全约束, 密码校验方式等
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.allowFormAuthenticationForClients()
.passwordEncoder(bCryptPasswordEncoder)
.tokenKeyAccess("permitAll()") //oauth/token_key是公开
.checkTokenAccess("permitAll()") //oauth/check_token公开
;
}
/**
* 用来配置客户端详情服务(ClientDetailsService), 客户端详情信息在这里进行初始化
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsServiceImpl);
}
/**
* 用来配置令牌(token)的访问端点和令牌服务
* 配置令牌的访问端点:即申请令牌的URL .pathMapping(defaultPath, customPath)
* 令牌服务:令牌的生成和发放规则(TokenConfig)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsServiceImpl)
.authenticationManager(authenticationManager)//认证管理器,Password模式配置所需
.authorizationCodeServices(authorizationCodeServices())//授权码模式code存储方式定义
.tokenStore(jwtTokenStore)//采用jwt方式管理token
.accessTokenConverter(jwtAccessTokenConverter)//jwt增强,定义自己的SigningKey
.allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET)
.reuseRefreshTokens(false)//表示重复使用刷新令牌。也就是说会一直重复使用第一次请求到的 refresh_token, 所以要禁止掉
;
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
//设置授权码模式的授权码如何存取,暂采用内存方式
return new InMemoryAuthorizationCodeServices();
}
}
以下是同步到语雀的、可读性好一点,CSDN 继续看的点专栏就好。
Oauth2.0 核心篇
Oauth2.0 安全性(以微信授权登陆为例)
Oauth2.0 认证服务器搭建
Oauth2.0 添加验证码登陆方式
Oauth2.0 资源服务器搭建
Oauth2.0 自定义响应值以及异常处理
Oauth2.0 补充