这篇讲的内容是:Oauth2在SpringBoot/SpringCloud中的实战。
SpringBoot版本:2.2.5.Release
SpringCloud版本:Hoxton.SR9
JDK版本:1.8
- <dependencies>
-
- <dependency>
- <artifactId>spring-cloud-starter-oauth2artifactId>
- <groupId>org.springframework.cloudgroupId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-data-redisartifactId>
- dependency>
-
- <dependency>
- <groupId>org.jasyptgroupId>
- <artifactId>jasyptartifactId>
- <version>1.9.2version>
- dependency>
- dependencies>
- @Configuration
- @EnableAuthorizationServer
- public class WebAuthorizationConfig extends AuthorizationServerConfigurerAdapter {
-
- private final AuthenticationManager authenticationManager;
- private final UserDetailsService userDetailsService;
- private final PasswordEncoder passwordEncoder;
- private final TokenStore tokenStore;
- private final AuthorizationCodeServices authorizationCodeServices;
- private final AuthTokenExceptionHandler authTokenExceptionHandler;
-
- public WebAuthorizationConfig(AuthenticationManager authenticationManager,
- UserDetailsService userDetailsService,
- PasswordEncoder passwordEncoder,
- TokenStore tokenStore,
- AuthorizationCodeServices authorizationCodeServices,
- AuthTokenExceptionHandler authTokenExceptionHandler) {
- this.authenticationManager = authenticationManager;
- this.userDetailsService = userDetailsService;
- this.passwordEncoder = passwordEncoder;
- this.tokenStore = tokenStore;
- this.authorizationCodeServices = authorizationCodeServices;
- this.authTokenExceptionHandler = authTokenExceptionHandler;
- }
-
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
- String secret = PasswordHelper.encryptPassword(Oauth2ClientUserEnums.ADMIN.getClientSecret());
-
- clients.inMemory()
- .withClient(Oauth2ClientUserEnums.ADMIN.getClientId())
- .secret(secret)
- .scopes("all", "test")
- .resourceIds("admin")
- // autoApprove 可跳过授权页直接返回code
- .autoApprove("all")
- .redirectUris("http://www.baidu.com")
- //客户端认证所支持的授权类型 1:客户端凭证 2:账号密码 3:授权码 4:token刷新 5:简易模式
- .authorizedGrantTypes(CLIENT_CREDENTIALS, PASSWORD, REFRESH_TOKEN, AUTHORIZATION_CODE, IMPLICIT)
- //用户角色
- .authorities("admin")
- //允许自动授权
- .autoApprove(false)
- //token 过期时间
- .accessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(12))
- //refresh_token 过期时间
- .refreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30))
- ;
- }
-
- @Override
- public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
- security
- .passwordEncoder(passwordEncoder) //设置密码编辑器
- .allowFormAuthenticationForClients()
- .tokenKeyAccess("permitAll()") //开启 /oauth/token_key 的访问权限控制
- .checkTokenAccess("permitAll()") //开启 /oauth/check_token 验证端口认证权限访问
- ;
- }
-
- @Override
- public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
- // 配置授权服务器端点的属性
- endpoints.authenticationManager(authenticationManager) //认证管理器
- .tokenStore(tokenStore)
- .authorizationCodeServices(authorizationCodeServices)
- .userDetailsService(userDetailsService)
- .exceptionTranslator(authTokenExceptionHandler);
- }
- }
注解:@EnableAuthorizationServer表明当前服务是认证服务。
介绍一下几个基础组件
①:authenticationManager
认证管理器,对客户端凭证、用户进行认证的地方。
②:tokenStore
存放token的地方,默认是存放在Inmemory(内存)中的。
③:authorizationCodeServices
code生成服务,使用默认的即可。
④:userDetailsService
用户详情服务,可重写实现,用户信息从数据库中加载。
⑤:authTokenExceptionHandler
自定义的 token 鉴别失败异常处理器。
⑥:authClientExceptionHandler
自定义的 客户端凭证 鉴别失败异常处理器。
- @Configuration
- @EnableResourceServer
- public class WebResourceConfig extends ResourceServerConfigurerAdapter {
-
- private final AuthClientExceptionHandler authClientExceptionHandler;
-
- public WebResourceConfig(AuthClientExceptionHandler authClientExceptionHandler) {
- this.authClientExceptionHandler = authClientExceptionHandler;
- }
-
- @Override
- public void configure(ResourceServerSecurityConfigurer resources) {
- resources.resourceId("admin").stateless(true).authenticationEntryPoint(authClientExceptionHandler);
- }
-
- @Override
- public void configure(HttpSecurity http) throws Exception {
- // 资源链路
- http
- .sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
- .and().formLogin().permitAll()
- // 登录放通
- .and()
- .authorizeRequests()
- .antMatchers("/oauth/**", "/favicon.ico")
- //.authenticated()
- .permitAll()
- // 其他请求都需认证
- .and()
- .authorizeRequests()
- .anyRequest()
- .authenticated()
- // 跨域
- .and()
- .cors()
- // 关闭跨站请求防护
- .and()
- .csrf()
- .disable();
- }
-
- }
注解:@EnableResourceServer表明当前服务是认证服务。
笔记:
为什么要放开 /favicon.ico 的访问权限?因为在进行授权码模式的时候,一直无法跳转到我定义的redirect_uri 地址中去。
使用 logging.level.org.springframework.security=debug 发现会访问 /favicon.ico ,然后报错,再然后就是调到登录页去了。
因此 /favicon.ico 放开之后,认证正常,成功调到redirect_uri 地址并返回了code。(这是OAuth2的小坑吗?)
- @Configuration
- @EnableWebSecurity
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-
- private final PasswordEncoder passwordEncoder;
-
- public WebSecurityConfig(PasswordEncoder passwordEncoder) {
- this.passwordEncoder = passwordEncoder;
- }
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth
- .userDetailsService(userDetailsService())
- .passwordEncoder(passwordEncoder); //为认证管理器配置passwordEncoder,无论客户端凭证密码还是用户密码都通过passwordEncoder进行密码匹配
- }
-
- @Bean
- @Override
- protected UserDetailsService userDetailsService() {
- return new VipUserDetailService();
- }
-
- @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
-
- }
详细配置可以翻阅我的GitHub:GitHub地址
注意!!!一定要配置redirect_uri,并且一定要允许表单登录!!!
以下是步骤:
①:浏览器输入以下链接地址,会重定向到登录页
http://localhost:8123/oauth/authorize?client_id=admin&client_secret=admin&response_type=code&redirect_uri=http://www.baidu.com&scope=test

②:输入配置的用户名密码,回调到授权页

如果我们配置的scope是autoApprove的,即可跳过这步,直接到第③步,拿到code。
③:选择 Approve,点击Authorize 即可调到redirect_uri地址

④:拿code 换 token
我的请求地址:127.0.0.1:8123/oauth/token?grant_type=authorization_code&code=h35Fh1&redirect_uri=http://www.baidu.com
需要配置Authorization,请求时会将客户端凭证以base64格式放在请求头中。

我的请求地址:127.0.0.1:8123/oauth/token?grant_type=password&username=found&password=123456
需要配置Authorization,请求时会将客户端凭证以base64格式放在请求头中。

我的请求地址:127.0.0.1:8123/oauth/token?grant_type=client_credentials
需要配置Authorization,请求时会将客户端凭证以base64格式放在请求头中。

浏览器输入以下地址即可拿到token:
http://localhost:8123/oauth/authorize?client_id=admin&client_secret=admin&response_type=token&redirect_uri=http://www.baidu.com

我的请求地址:127.0.0.1:8123/oauth/token?grant_type=refresh_token&refresh_token=dde5f388-4ad7-4781-a1b7-aaafb3c34b10
refresh_token是服务器颁发给客户端的,作用就是在一定时间内,让用户不用重新登录。
需要配置Authorization,请求时会将客户端凭证以base64格式放在请求头中。

个人认为做第三方登录是当前比较流行的做法。
①:比如我们现在登录ProcessOn,就可以选择第三方登录。

②:点击QQ登录后会调到这个页面↓↓↓↓↓↓↓↓↓↓↓↓

③:上面这个client_id 应该就是ProcessOn 官方申请的第三方客户端凭证了。
选择账号密码登录,输入账号密码之后就可以拿到token,(我手速快截了个图)

④:可以看到返回了access_token、过期时间。
随后processOn既发起请求进行登录操作,登录成功进入用户首页,失败则重定向到登录页。


可以看到第三方登录期间是不需要手动approve的,因此scope=all是autoApprove的。
认真辨别了一下,QQ第三方登录采用不是授权码模式,而是Implicat Grant Type。(差点看走眼)
总结 :
学习总要从身边找例子,这样才能更好的理解。