Oauth2-授权码模式与密码模式获取jwt令牌,以及校验、刷新jwt令牌


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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM