简体   繁体   中英

Spring Security Oauth2 only show a login page for non-api requests

I am using spring boot 1.5.6 and security with OAuth2 and JWT

~/repos/jtor > mvn dependency:tree | grep security
[INFO] +- org.springframework.security.oauth:spring-security-oauth2:jar:2.0.14.RELEASE:compile
[INFO] |  +- org.springframework.security:spring-security-core:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework.security:spring-security-config:jar:4.2.3.RELEASE:compile
[INFO] |  +- org.springframework.security:spring-security-web:jar:4.2.3.RELEASE:compile
[INFO] +- org.springframework.security:spring-security-jwt:jar:1.0.8.RELEASE:compile
[INFO] \- org.springframework.security:spring-security-test:jar:4.2.3.RELEASE:test

With this configuration below, requests to both / and /api/person result in a standard OAuth 401:

{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}

What I would like is for requests to /api/** to get the 401 while any other requests (like / or /welcome ) to be redirected (302) to my Authorization server (Ping Federate). After logging in, I would be redirected back and my GET to / would return my HTML (or whatever).

In other words, calls made to the API would require a token, while non-api calls would follow the oauth2 code flow.

my config:

security:
  basic:
    enabled: false
  oauth2:
    client:
      accessTokenUri: https://ping-dev.my-comp.com:9031/as/token.oauth2
      userAuthorizationUri: https://ping-dev.my-comp.com:9031/as/authorization.oauth2
      logoutUri: https://ping-dev.my-comp.com:9031/ext/logout
      clientId: oauth2
      clientSecret: foo
      authentication-scheme: header
    resource:
      jwt:
        keyValue: |
          -----BEGIN PUBLIC KEY-----
          MY KEY HERE
          -----END PUBLIC KEY-----

my application

@SpringBootApplication
@Slf4j
public class JtorApplication {

    public static void main(String[] args) {
        SpringApplication.run(JtorApplication.class, args);
    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
           http
                   .authorizeRequests()
                   .antMatchers("/info", "/health", "/public/**").permitAll()
                   .anyRequest().fullyAuthenticated();
           // @formatter:on
        }
    }

    /*
     this class allows me to access public resources with a URL that starts with "public/", which
     allows me to bypass security on any public resource in a clean way.
     */
    @Configuration
    protected static class WebMvcConfigurer extends WebMvcConfigurerAdapter {
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/public/**").addResourceLocations("classpath:/public/");
        }
    }

    /*
    This is the API I want protected via Oauth2/JWT.
    Only calls containing a valid Bearer token should be allowed.
    Calls without a token should get back a standard Oauth 401 error
     */
    @RestController
    @RequestMapping("/api")
    protected static class ApiController {

        @GetMapping("/person")
        public Person getPerson() {
            return Person.builder().id(1).name("Jason").build();
        }

        @Getter
        @Builder
        static class Person {
            private int id;
            private String name;
        }
    }

    /*
    This is the "web app" controller that returns an "app", which will make ajax calls to the api.
    I want this route to be secured, but instead of just returning a 401, I want to start the
    OAuth login process by sending the user to my OAuth Server
     */
    @Controller
    protected static class WebAppController {

        @RequestMapping("/")
        public String webapp() {
            return "index";
        }

        // this should bypass security because it starts with "public"
        @RequestMapping("/public")
        public String publicPage() {
            return "public";
        }

    }

}

If you want to handle different parts of your application with different authentication mechanisms, you can just create additional configuration class extending WebSecurityConfigurerAdapter and specify your security constraints there.

Make sure that you specify the URLs, to which your security config should be applied, in an AntMatcher before calling .authorizeRequests() . See code below.

You can set the order of your security configurations using @Order annotation.

Example with HTTP basic authentication

@Configuration
@Order(2) //will be applied after @Order(1), etc...
protected AnotherSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
       http
            .antMatcher("/api/**") //apply only to api endpoints
            .authorizeRequests()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .and()
            .httpBasic(); //set to basic auth
    }
}

Try this:

    protected void configure(HttpSecurity http) throws Exception {
        http
....
        .and()
            .exceptionHandling().authenticationEntryPoint(entryPoint())
....
    }

    private AuthenticationEntryPoint entryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                //your code here - can key off of the request
            }
        };
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM