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 ServerBuild 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@EnableAuthorizationServermaintenance 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 + ":&nbsp" + 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"),它仅适用于 USERROLE_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 ServerSpring 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

Spring Boot + Spring Security + Spring OAuth2 + Google 登录

Spring Security +Oauth2 +Spring boot 动态定义权限