Spring Boot 2.5.3 OAuth2 - Auth-Server 和 Web Service 分开,登录后没有端点工作
Posted
技术标签:
【中文标题】Spring Boot 2.5.3 OAuth2 - Auth-Server 和 Web Service 分开,登录后没有端点工作【英文标题】:Spring Boot 2.5.3 OAuth2 - Auth-Server and Webservice separate, after Login no endpont works 【发布时间】:2021-10-07 15:19:37 【问题描述】:按照https://developer.okta.com/blog/2019/03/12/oauth2-spring-security-guide 上的示例,在登录后使用项目Create an OAuth 2.0 Server
和Build Your Client App
,任何访问的端点都会跳转到根端点localhost:8082
。
我不使用Thymeleaf
,因为我的网络服务返回数据,而不是页面。
OAuth 2.0 服务器项目
@SpringBootApplication
@EnableResourceServer
public class Demo2Application
public static void main(String[] args)
SpringApplication.run(Demo2Application.class, args);
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter
private final PasswordEncoder passwordEncoder;
public AuthorizationServerConfig(PasswordEncoder passwordEncoder)
this.passwordEncoder = passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
clients.inMemory()
.withClient("abcd")
.secret(passwordEncoder.encode("fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9"))
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true)
.redirectUris("http://localhost:8082/login/oauth2/code/");
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");;
@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("doe"))
.roles("USER");
@Bean
public BCryptPasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
@RestController
public class UserController
@GetMapping("/user/me")
public Principal user(Principal principal)
return principal;
application.properties
server.port=8090
pom.xml
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我省略了项目最初使用的上下文路径。
网络服务项目
@RestController
public class MyRESTController
@GetMapping("/securedPage")
public String securedPage(Principal principal)
return "securedPage";
@GetMapping("/")
public String index(Principal principal)
return "index";
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
application.properties
server.port=8082
server.servlet.session.cookie.name=UISESSION
spring.security.oauth2.client.registration.custom-client.client-id=abcd
spring.security.oauth2.client.registration.custom-client.client-secret=fDw7Mpkk5czHNuSRtmhGmAGL42CaxQB9
spring.security.oauth2.client.registration.custom-client.client-name=Auth Server
spring.security.oauth2.client.registration.custom-client.provider=custom-provider
spring.security.oauth2.client.registration.custom-client.scope=user_info
spring.security.oauth2.client.registration.custom-client.redirect-uri=http://localhost:8082/login/oauth2/code/
spring.security.oauth2.client.registration.custom-client.client-authentication-method=basic
spring.security.oauth2.client.registration.custom-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.custom-provider.authorization-uri=http://localhost:8090/oauth/authorize
spring.security.oauth2.client.provider.custom-provider.token-uri=http://localhost:8090/oauth/token
spring.security.oauth2.client.provider.custom-provider.user-info-uri=http://localhost:8090/user/me
spring.security.oauth2.client.provider.custom-provider.user-name-attribute=name
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
效果很好。
现在,当我将 OAuth 2.0 服务器项目与我的 web 服务项目一起使用时,意味着我将演示 web 服务项目中的应用程序属性添加到我的 application.properties
中,除了根端点 http://localhost:8082
之外,我无法访问任何其他端点。
我认为我的 web 服务项目中的 WebSecurityConfig
是原因。它看起来像这样:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
public static final String ROLE_MYUSER = "MYUSER";
public static final String ROLE_MYADMIN = "MYADMIN";
private MyUserProperties myUserProperties;
public WebSecurityConfig(MyUserProperties myUserProperties)
// Load usernames and passwords from properties file
this.myUserProperties = myUserProperties;
@Override
protected void configure(HttpSecurity http) throws Exception
http
//.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/abc/**").hasRole(ROLE_MYUSER)
.mvcMatchers(HttpMethod.GET, "/def1/name", "/def2/name").hasRole(ROLE_MYUSER)
.mvcMatchers(HttpMethod.PATCH, "/def/name", "/def2/name").hasRole(ROLE_MYUSER)
.antMatchers("/ghi/**").hasRole(ROLE_MYUSER)
//... and so on
.antMatchers("/**").hasRole(ROLE_MYADMIN)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2Login();
//.csrf().disable()
//.formLogin().disable();
@Bean
@Override
public UserDetailsService userDetailsService()
UserDetails myuser = User
.withUsername(myUserProperties.getPortal().get("user"))
.password("noop" + myUserProperties.getPortal().get("pass"))
.roles(ROLE_MYUSER)
.build();
UserDetails myadmin = User
.withUsername(myUserProperties.getAdmin().get("user"))
.password("noop" + myUserProperties.getAdmin().get("pass"))
.roles(ROLE_MYUSER, ROLE_MYADMIN)
.build();
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(myuser);
userDetailsManager.createUser(myadmin);
return userDetailsManager;
到目前为止,我有Basic Auth
,我用两个角色定义了我的两个用户
@Bean
@Override
public UserDetailsService userDetailsService()
现在切换到我删除的 OAuth2
@Override
protected void configure(HttpSecurity http) throws Exception
线条
.httpBasic()
.csrf().disable()
.formLogin().disable()
并添加
.oauth2Login()
网络浏览器中的行为如下
http://localhost:8082
从该根端点正确传递数据,无需任何登录。
任何其他端点,例如
http://localhost:8082/abc
首先进入登录页面,在这里我从 OAuth 2.0 服务器项目中输入定义的用户 john / doe
,之后它不会显示来自端点 http://localhost:8082/abc
的预期数据,而是跳回根端点http://localhost:8082
.
我尝试的第一步是放置
@Bean
@Override
public UserDetailsService userDetailsService()
在OAuth 2.0 Server项目中,进入类
@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
但是登录不起作用,它只接受john / doe
。
下一步是删除
@Bean
@Override
public UserDetailsService userDetailsService()
也来自该类并在中定义我的两个用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
john / doe
的定义位置。
现在可以使用我的一个用户登录,但错误行为仍然存在。 还有什么需要改变的,在哪里?
对我的 web 服务项目中端点的访问取决于用户拥有的角色。
我的pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
【问题讨论】:
首先@EnableResourceServer
和@EnableAuthorizationServer
在maintenance mode
中,这基本上意味着它们已被弃用。你所指的博文已有2年多的历史了,所以那里有很多错误的信息,没有更新。其次,没有任何日志就不可能知道任何事情。
是的,你说得对,但是 Spring Boot OAuth2 项目还在更新,现在是 2.5.2。 docs.spring.io/spring-security-oauth2-boot/docs/2.5.2/reference/…
记录的不多。在登录或任何端点请求其他 lan localhost:8082 之后,它会跳转到我的 REST 控制器,该控制器服务于“/”路径。记录什么以及如何记录?
【参考方案1】:
我必须指出 OAuth 2.0 Server 项目和我所依赖的 Client 项目 (https://developer.okta.com/blog/2019/03/12/oauth2-spring-security-guide)。
在 OAuth 2.0 Server 项目中使用角色 ADMIN
定义用户 admin
时
@Configuration
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
;
...
并在客户端项目WebSecurityConfig
中修改为具有路径/helloworld
,其中只有角色ADMIN
才能访问
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.antMatchers("/helloworld").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.oauth2Login();
并修改 REST 控制器 MyRESTController
以提供该路径
@GetMapping("/helloworld")
public String helloworld(Principal principal)
return "helloworld";
还有一个额外的路径 /oauthinfo
提供 OAuth 信息
@GetMapping("/oauthinfo")
public String oauthUserInfo(
@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
@AuthenticationPrincipal OAuth2User oauth2User)
return "User Name: " + oauth2User.getName() + "<br/>"
+ "User Authorities: " + oauth2User.getAuthorities() + "<br/>"
+ "Client Name: " + authorizedClient.getClientRegistration().getClientName() + "<br/>"
+ this.prettyPrintAttributes(oauth2User.getAttributes());
private String prettyPrintAttributes(Map<String, Object> attributes)
String acc = "User Attributes: <br/><div >";
for (String key : attributes.keySet())
Object value = attributes.get(key);
acc += "<div>" + key + ": " + value.toString() + "</div>";
return acc + "</div>";
我观察到以下几点:
使用网络浏览器访问路径/securedPage
导致登录,使用admin / admin123
登录成功,我在网络浏览器中看到securedPage
文本。
然后访问路径/helloworld
会导致一个
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Aug 03 09:50:50 CEST 2021
There was an unexpected error (type=Forbidden, status=403).
登录用户的角色ADMIN
也应该被允许访问路径。
访问/oauthinfo
路径显示如下:
User Name: admin
User Authorities: [ROLE_USER, SCOPE_user_info]
Client Name: Auth Server
User Attributes:
authorities: [authority=ROLE_ADMIN]
details: remoteAddress=127.0.0.1, sessionId=null, tokenValue=W9T0llypwFCfgmXZDMyQTnMbOYc, tokenType=Bearer, decodedDetails=null
authenticated: true
userAuthentication: authorities=[authority=ROLE_ADMIN], details=remoteAddress=0:0:0:0:0:0:0:1, sessionId=4DF4FBDFA08F0C8F19B347C0B17550FC, authenticated=true, principal=password=null, username=admin, authorities=[authority=ROLE_ADMIN], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true, credentials=null, name=admin
oauth2Request: clientId=abcd, scope=[user_info], requestParameters=code=wnYl7w, grant_type=authorization_code, scope=user_info, response_type=code, state=z0MyCHpWUVVeVgLIP6vuP4ELzETmN5sziM-gZxBKsY0=, redirect_uri=http://localhost:8082/login/oauth2/code/, client_id=abcd, resourceIds=[], authorities=[], approved=true, refresh=false, redirectUri=http://localhost:8082/login/oauth2/code/, responseTypes=[code], extensions=, grantType=authorization_code, refreshTokenRequest=null
principal: password=null, username=admin, authorities=[authority=ROLE_ADMIN], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true
credentials:
clientOnly: false
name: admin
看起来,/helloworld
的路径限制中比较的角色不是ADMIN
(权限ROLE_ADMIN
),而是USER
(权限ROLE_USER
),它在第二行中给出输出:
User Authorities: [ROLE_USER, SCOPE_user_info]
如果我将其从 .hasRole("ADMIN")
更改为 .hasAuthority("ROLE_ADMIN")
,它仅适用于 USER
和 ROLE_USER
。
我不明白这里的问题是什么。 在 OAuth 2.0 Server 项目中定义用户是否正确?
总体而言,问题是:这是实现我想要实现的目标的正确方法甚至库吗?
使用Basic Auth
我的WebSecurityConfig
给出了一些路径限制,具体取决于登录用户的角色。
从Basic Auth
切换到OAuth2
,将Authorization Server
作为一个单独的项目需要修改什么?
我所依赖的 okta-developer 项目的代码可在 https://github.com/oktadev/okta-spring-boot-authz-server-example 获得
如果可以在那个简单的OAuth 2.0 Server
和Spring Boot OAuth 2.0 Client
项目中首先解决角色的路径限制,那么我相信我的webservice 项目也可以工作。
【讨论】:
【参考方案2】:这里的解决方案OAuth2 Client Principal do not have GrantedAuthorities when authenticated by Other Custom Authorization Server (SpringBoot2 & OAuth2)
解决了。
CustomOAuth2User
CustomOAuth2UserService
CustomAuthoritiesExtractor
【讨论】:
以上是关于Spring Boot 2.5.3 OAuth2 - Auth-Server 和 Web Service 分开,登录后没有端点工作的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 和 OAuth2 社交登录,无法获取 refreshToken
使用 spring-boot OAuth2 服务器保护的 Spring-boot 应用程序
让 oauth2 与 spring-boot 和 rest 一起工作
Spring Boot Restful WebAPI集成 OAuth2