1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

时间:2023-03-23 12:01:28

相关推荐

搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}

http://localhost:8761/eureka/ 是因为配置文件未提供注册中心地址,springcloud 默认的注册中心地址就是这个

另外:文末会提供所有代码

Oauth2.0 有以下四种授权模式:本文介绍 授权码认证

1、授权码模式(Authorization Code)[常用]

2、隐式授权模式(Implicit)[不常用]

3、密码模式(Resource Owner Password Credentials)[常用]

4、客户端模式(Client Credentials)[不常用]

授权码认证流程

客户端请求第三方授权用户(资源拥有者)同意给客户端授权客户端获取到授权码,请求认证服务器申请 令牌认证服务器向客户端响应令牌客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权资源服务器返回受保护资源
客户端请求第三方授权

Get请求:http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=9001:是你的认证服务器的端口参数解释:client_id:客户端id,和授权配置类中设置的客户端id一致。 response_type:授权码模式固定为code scop:客户端范围,和授权配置类中设置的scop一致。 redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)

首先我们进入的是登录页面,用户名密码则是客户端ID,和密钥,当前我们配置在内存中,后面将会采用数据库方式所以在浏览器中输入的地址是:http://localhost:9001/oauth/authorize?client_id=oauth&response_type=code&scop=app&redirect_uri=在正确输入客户端id和密钥之后输入以上地址回车

当我们点击授权的时候,将会将请求转发到你指定的 uri 上去,也就是

此时我们拿到了授权码之后就可以向认证服务器去请求 token:地址为:http://localhost:9001/oauth/token POST请求

参数解释:

grant_type:授权类型,填写authorization_code,表示授权码模式

code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。

redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。

此链接需要使用 http Basic认证。 什么是http Basic认证? http协议定义的一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端,一个例子: Authorization:Basic WGNXZWJBcHA6WGNXZWJBcHA=WGNXZWJBcHA6WGNXZWJBcHA= 是用户名:密码的base64编码。 认证失败服务端返回 401 Unauthorized。

返回参数解释:

access_token:访问令牌,携带此令牌访问资源

token_type:有MAC Token与Bearer Token两种类型,两种的校验算法不同,RFC 6750建议Oauth2采用 Bearer Token(/#rfc6750)。

refresh_token:刷新令牌,使用此令牌可以延长访问令牌的过期时间。

expires_in:过期时间,单位为秒。

scope:范围,与定义的客户端范围一致。

jti:当前token的唯一标识

令牌校验地址:http://localhost:9001/oauth/check_token?token= [access_token] GET请求,access_token 就是刚刚申请的 token刷新令牌地址:http://localhost:9001/oauth/token POST请求,参数:grant_type: 固定为 refresh_token,refresh_token:刷新令牌(注意不是access_token,而是refresh_token)

以上就是授权码授权,且采用内存配置客户端ID和密钥方式

下面介绍一下使用数据库方式获取客户端ID和密钥方式

首先更改配置类 AuthorizationServerConfig

@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.jdbc(dataSource).clients(clientDetails());//使用 JDBC 模式// clients.inMemory()//.withClient("oauth") // 客户端ID//.secret("oauth") // 客户端密钥//.redirectUris("")// 跳转地址//.accessTokenValiditySeconds(3600) // token 有效时间//.refreshTokenValiditySeconds(3600) // 刷新token有效时间//.authorizedGrantTypes(// "authorization_code", // 根据授权码生成令牌// "client_credentials", // 客户端认证// "refresh_token", // 刷新令牌// "password") // 密码方式认证//.scopes("app");}/*** 客户端配置** @return ClientDetailsService*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource); // dataSource 为注入的数据源}

改完我们继续之前的操作肯定会报错,因为采用 JDBC 方式必定数据库应该有对应的表数据,所以我们查看 JdbcClientDetailsService 的实现方式,发现在 loadClientByClientId 方法中的 sql 语句,如下图

通过看源码,可以发现 ClientDetailsService 的 JDBC 实现必须要数据库有一张表结构

所以我们建立数据库表结构

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for oauth_client_details-- ----------------------------DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details` (`client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ID,主要用于标识对应的应用',`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端秘钥,BCryptPasswordEncoder加密算法加密',`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '对应的范围',`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '认证模式',`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '认证后重定向地址',`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`access_token_validity` int(11) NULL DEFAULT NULL COMMENT '令牌有效期',`refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT '令牌刷新周期',`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`client_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;SET FOREIGN_KEY_CHECKS = 1;--- 因为之前数据是在内存中伪造的,所以我们也应该伪造一个客户端ID和密钥的信息在数据库中INSERT INTO `oauth_client_details` VALUES ('oauth', NULL, '$2a$10$1z1vevmlMKwlw2YxbQxc0e1IY7ZME1nW35T123O1lzYfEk5YrJe4O', 'app', 'authorization_code,password,refresh_token,client_credentials', '', NULL, 432000000, 432000000, NULL, NULL);

再继续前几个步骤就一样能够授权成功

源码部分

1、最重要的 AuthorizationServerConfig 认证服务器配置类

@Configuration// 表示开启授权服务器,拥有以下路径可访问// /oauth/authorize,// /oauth/token,// /oauth/check_token,// /oauth/confirm_access,// /oauth/error// /oauth/login// /oauth/logout@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 数据源,用于从数据库获取数据进行认证操作,测试可以从内存中获取*/@Autowiredprivate DataSource dataSource;/*** jwt令牌转换器*/@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;/*** SpringSecurity 用户自定义授权认证类*/@Autowiredprivate UserDetailsService userDetailsService;/*** 授权认证管理器*/@Autowiredprivate AuthenticationManager authenticationManager;/*** 令牌持久化存储接口*/@Autowiredprivate TokenStore tokenStore;@Resource(name = "keyProp")private KeyProperties keyProperties;/*** 客户端信息配置** @param clients 客户端* @throws Exception exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.jdbc(dataSource).clients(clientDetails());// clients.inMemory()//.withClient("oauth") // 客户端ID//.secret("oauth") // 客户端密钥//.redirectUris("")// 跳转地址//.accessTokenValiditySeconds(3600) // token 有效时间//.refreshTokenValiditySeconds(3600) // 刷新token有效时间//.authorizedGrantTypes(// "authorization_code", // 根据授权码生成令牌// "client_credentials", // 客户端认证// "refresh_token", // 刷新令牌// "password") // 密码方式认证//.scopes("app");}/*** 授权服务器端点配置** @param endpoints endpoints*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.accessTokenConverter(jwtAccessTokenConverter).authenticationManager(authenticationManager) //认证管理器.tokenStore(tokenStore) //令牌存储.userDetailsService(userDetailsService);//用户信息service}/*** 授权服务器的安全配置** @param oauthServer oauthServer*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) {oauthServer.allowFormAuthenticationForClients().passwordEncoder(new BCryptPasswordEncoder()) // 密码加密器.tokenKeyAccess("permitAll()") // 允许所有人请求令牌.checkTokenAccess("isAuthenticated()"); // 已验证的客户才能请求 check_token 接口验证 token 有效性}/*** 读取密钥的配置** @return KeyProperties*/@Bean("keyProp")public KeyProperties keyProperties() {return new KeyProperties();}/*** 客户端配置** @return ClientDetailsService*/@Beanpublic ClientDetailsService clientDetails() {return new JdbcClientDetailsService(dataSource);}@Bean@Autowiredpublic TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {return new JwtTokenStore(jwtAccessTokenConverter);}/*** JWT令牌转换器** @param customUserAuthenticationConverter customUserAuthenticationConverter* @return JwtAccessTokenConverter*/@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();KeyPair keyPair = new KeyStoreKeyFactory(keyProperties.getKeyStore().getLocation(),//证书路径 changgou.jkskeyProperties.getKeyStore().getSecret().toCharArray()) //证书秘钥 changgou.getKeyPair(keyProperties.getKeyStore().getAlias(), //证书别名 changgoukeyProperties.getKeyStore().getPassword().toCharArray()); //证书密码 changgouconverter.setKeyPair(keyPair);//配置自定义的 CustomUserAuthenticationConverterDefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);return converter;}}

2、UserDetailsServiceImpl

/*** 自定义授权认证类*/@Slf4j@Service// 标识为主要实现类,自动注入会优先选择此实现类@Primarypublic class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate ClientDetailsService clientDetailsService;/*** 自定义授权认证** @param username username* @return UserDetails* @throws UsernameNotFoundException e*/@Overridepublic UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException {// 取出身份,如果身份为空说明没有认证Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 没有认证统一采用httpBasic认证,httpBasic中存储了client_id 和 client_secret,开始认证 client_id 和 client_secretif (authentication == null) {// AuthorizationServerConfig.java 配置了 ClientDetails 的实现方式为 JdbcClientDetailsService// 查看 JdbcClientDetailsService 源码可知其提供了客户端凭证的 增删改查 方法,这里主要使用了 根据用户名查找的方式try {ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);if (clientDetails != null) {//秘钥String clientSecret = clientDetails.getClientSecret();// 通过 客户端id和密钥访问系统:静态方式就是密钥未 BCryptPasswordEncoder() 加密编码//静态方式:return new User(username,clientSecret, maSeparatedStringToAuthorityList(""));return new User(username, clientSecret, maSeparatedStringToAuthorityList(""));}} catch (ClientRegistrationException e) {log.error(String.format("客户端ID %s 不存在!", username));throw new UsernameNotFoundException(String.format("客户端ID %s 不存在!", username));}}if(username == null){return null;}}

3、WebSecurityConfig

/*** EnableWebSecurity: 开启 SpringSecurity* Order: 值越小,越优先被加载*/@Configuration@EnableWebSecurity@Order(-1)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 忽略安全拦截的URL** @param web web*/@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/oauth/login", "/oauth/logout");}/*** 创建授权管理认证对象** @return AuthenticationManager* @throws Exception e*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 采用BCryptPasswordEncoder对密码进行编码** @return PasswordEncoder*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** @param http http* @throws Exception e*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable() //.httpBasic() //启用Http基本身份验证.and().formLogin() //启用表单身份验证.and().authorizeRequests() //限制基于Request请求访问.anyRequest().authenticated(); //其他请求都需要经过验证}}

下一篇:密码授权流程快速通道:密码授权流程

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。