Oauth2查询的存有客户端(不是个人用户,而是第三方登录时 人-客户端-第三方的那个客户端)账号密码的表必须叫oauth_client_details
Oauth2有以下授权模式:
1.授权码模式(Authorization Code)
2.隐式授权模式(Implicit)
3.密码模式(Resource Owner Password Credentials)
4.客户端模式(Client credentials)
导入day09资源里的changgou-user-oath这个模块,其中的关键是这四个配置类,和引导类
1
package com.changgou.oauth.config;
import com.changgou.oauth.util.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration @EnableAuthorizationServer class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { //数据源,用于从数据库获取数据进行认证操作,测试可以从内存中获取 @Autowired private DataSource dataSource; //jwt令牌转换器 @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; //SpringSecurity 用户自定义授权认证类 @Autowired UserDetailsService userDetailsService; //授权认证管理器 @Autowired AuthenticationManager authenticationManager; //令牌持久化存储接口 @Autowired TokenStore tokenStore; @Autowired private CustomUserAuthenticationConverter customUserAuthenticationConverter; /*** * 客户端信息配置 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource).clients(clientDetails()); } /*** * 授权服务器端点配置 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.accessTokenConverter(jwtAccessTokenConverter) .authenticationManager(authenticationManager)//认证管理器 .tokenStore(tokenStore) //令牌存储 .userDetailsService(userDetailsService); //用户信息service } /*** * 授权服务器的安全配置 * @param oauthServer * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.allowFormAuthenticationForClients() .passwordEncoder(new BCryptPasswordEncoder()) .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } //读取密钥的配置 @Bean("keyProp") public KeyProperties keyProperties(){ return new KeyProperties(); } @Resource(name = "keyProp") private KeyProperties keyProperties; //客户端配置 @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } @Bean @Autowired public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } /**** * JWT令牌转换器 * @param customUserAuthenticationConverter * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyPair keyPair = new KeyStoreKeyFactory( keyProperties.getKeyStore().getLocation(), //证书路径 changgou.jks keyProperties.getKeyStore().getSecret().toCharArray()) //证书秘钥 changgouapp .getKeyPair( keyProperties.getKeyStore().getAlias(), //证书别名 changgou keyProperties.getKeyStore().getPassword().toCharArray()); //证书密码 changgou converter.setKeyPair(keyPair); //配置自定义的CustomUserAuthenticationConverter DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter(); accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter); return converter; } }
package com.changgou.oauth.config; import com.changgou.oauth.util.UserJwt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.Map; @Component public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter { @Autowired UserDetailsService userDetailsService; @Override public Map<String, ?> convertUserAuthentication(Authentication authentication) { LinkedHashMap response = new LinkedHashMap(); String name = authentication.getName(); response.put("username", name); Object principal = authentication.getPrincipal(); UserJwt userJwt = null; if(principal instanceof UserJwt){ userJwt = (UserJwt) principal; }else{ //refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt UserDetails userDetails = userDetailsService.loadUserByUsername(name); userJwt = (UserJwt) userDetails; } response.put("name", userJwt.getName()); response.put("id", userJwt.getId()); if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) { response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities())); } return response; } }
3
package com.changgou.oauth.config; import com.changgou.oauth.util.UserJwt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; 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.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /***** * 自定义授权认证类 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired ClientDetailsService clientDetailsService; /**** * 自定义授权认证 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //取出身份,如果身份为空说明没有认证 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); //没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret if(authentication==null){ ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username); if(clientDetails!=null){ //秘钥 String clientSecret = clientDetails.getClientSecret(); //静态方式 //return new User(username,new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList("")); //数据库查找方式 return new User(username,clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList("")); } } if (StringUtils.isEmpty(username)) { return null; } //根据用户名查询用户信息 String pwd = new BCryptPasswordEncoder().encode("itheima"); //创建User对象 String permissions = "goods_list,seckill_list"; UserJwt userDetails = new UserJwt(username,pwd,AuthorityUtils.commaSeparatedStringToAuthorityList(permissions)); return userDetails; } }
4
package com.changgou.oauth.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.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @Order(-1) class WebSecurityConfig extends WebSecurityConfigurerAdapter { /*** * 忽略安全拦截的URL * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers( "/oauth/login", "/oauth/logout"); } /*** * 创建授权管理认证对象 * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } /*** * 采用BCryptPasswordEncoder对密码进行编码 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /**** * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .httpBasic() //启用Http基本身份验证 .and() .formLogin() //启用表单身份验证 .and() .authorizeRequests() //限制基于Request请求访问 .anyRequest() .authenticated(); //其他请求都需要经过验证 } }
授权码授权流程(获取jwt令牌)
1、客户端请求第三方授权
2、用户同意给客户端授权
3、客户端获取到授权码,请求认证服务器申请 令牌
4、认证服务器向客户端响应令牌
5、客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权
6、资源服务器返回受保护资源
1 获取授权码
此时数据库里的的数据为 其中changgou的密码为加密后的changgou
在浏览器地址栏输入
http://localhost:9200/oauth/authorize?client_id=changgou&response_type=code&scop=app&redirect_uri=http://localhost
这个地址是oauth2内部封装的,不是我们自己写的
账号密码都是changgou 这个账号密码不是用户个人的密码,而是第三方平台中保存的本系统的账号和密码
进入后点击授权,地址栏中的http://localhost/?code=qquEOA code就是授权码
这个授权码生成后只能使用一次
2 获取令牌
在postman中,编辑post请求
http://localhost:9200/oauth/token
这个地址是oauth2内部封装的,不是我们自己写
需要在body中携带三个参数(form-data)
grant_type : authorization_code 授权类型,authorization_code代表授权码模式
code: 授权码 上一步获取的授权码
redirect_uri: 申请授权码时的跳转url 和申请授权码时路径中的redirect_uri的值一致,此处为http://localhost
然后需要进行httpBasic认证,basic认证是一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端
在postman中选择Authorization,type选择basicAuth,右侧填入客户端的用户名和密码(都是changgou)
然后就可以发送请求了
获取到的值为
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYxMDgxOTQwNCwianRpIjoiMGM0YjA1YmItOTdhOS00ZjM2LWIzNWItOWFhMTQzYzlhNTk1IiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.NcInwpPDeSsS24-cIh6KBFuqGBht0oy2F6mHovRwvMpJMSAHCuwpIbt3Cw3n63lKVPzqeRYhX2iOVfj2qATBIuu98DTCLkrReoEhvW6khjpW1dMJNWubdFqfPQfDmE8AcSuCGX__2pivsRUVtiew6liAxqw-GL5rD6ClTrJpTcpblo3-5nk-YMrSvAoj_xnBO4WVWh1JQJ-C_NIr0s0VuEAuKH_UZLrMGLDUyHDDk07kLiAP44gRCA1oMjBVF-OvK-9NYueetiy92T9gA8vDY01jU3SYdSOm-_Ta6xVPVY2pt6uXvzSFZ3qvCrbRK4jwnMFH-eE72vM7mhLoRPhf_g", "token_type": "bearer", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiYXRpIjoiMGM0YjA1YmItOTdhOS00ZjM2LWIzNWItOWFhMTQzYzlhNTk1IiwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYxMDgxOTQwNCwianRpIjoiOTNjYWZiYTgtMzE5Zi00OTI2LWEwNTMtNzg1YTVmMmQyZTEzIiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.EgPYoKMWaBUvvSHq6Gx1SX560aUJ6N3nQg28w2EyPPACHa9k0nwn9zCbeTkVSxIfXKc7q3J6f1PqpG1eMRkSYjitOZFwvSldH91__Ij33noOf4kUbOCEfBISDtY-vcXdpb25TdkH7gTahNUMKQU6N-DnL3l3Smm22-WkDeRqpNXFY2atn36sMYX_P5Nid-0Vcf6jQKwkC5N6DD38WgEOrFsXLi7JawN7833XEDMGxLZVirAPBLWFJNLtuSk-BfIF38RsdhEiiEE1AYcQJiB0ZCVf7D9umYZ_U4wNpHTZFKODUuUurLSBDu1Fkk4ybRT06-W4aAov8xSFeaWaB61vlg", "expires_in": 43199, "scope": "app", "jti": "0c4b05bb-97a9-4f36-b35b-9aa143c9a595" }
access_token就是jwt令牌
refresh_token是刷新令牌,用于在令牌快过期时重置过期时间
expires_in 是过期时间
jti是jwt令牌的短标识,和access_token是一一对应的,我们可以把jwt令牌放进redis(jti=jwt的map),把jti放在cookie里,以节省空间
3 校验令牌
发送get请求
http://localhost:9200/oauth/check_token?token= jwt令牌值,不带引号
这个地址是oauth2内部封装的,不是我们自己写
正常解析说明校验成功
4 刷新令牌
刷新令牌不会产生新令牌,而是重置一个令牌的死亡时间
刷新令牌不需要授权码,不需要账号和密码,只需要一个refresh_token,客户端id和客户端密码
在postman中,编辑post请求
http://localhost:9200/oauth/token
这个地址是oauth2内部封装的,不是我们自己写
需要在body中携带两个参数(form-data)
grant_type : refresh_token 授权类型,refresh_token代表刷新令牌
refresh_token: 刷新令牌的值 注意不是jwt令牌,而是获取jwt令牌时一同获取的refresh_token
完成请求后,expires_in的值和初次获取令牌时一致
密码授权流程(获取jwt令牌)
密码模式(Resource Owner Password Credentials)与授权码模式的区别是申请令牌不再使用授权码
而是直接 通过用户名和密码即可申请令牌。
在postman中,编辑post请求
http://localhost:9200/oauth/token
这个地址是oauth2内部封装的,不是我们自己写
需要在body中携带三个参数(form-data)
grant_type: password password代表授权类型为密码模式
username: 账号 注意这个账号密码时用户个人在本系统注册的账号和密码
password: 密码 目前账号密码都是itheima,这和本文中开头导入的四个类中的第三个UserDetailsServiceImpl中的假数据有关,后期要改为从数据库中查
然后需要进行httpBasic认证,basic认证是一种认证方式,将客户端id和客户端密码按照“客户端ID:客户端密码”的格式拼接,并用base64编 码,放在header中请求服务端
在postman中选择Authorization,type选择basicAuth,右侧填入客户端的用户名和密码(都是changgou)
然后进行请求
即可获取相关令牌
1
2
1