I am trying to familiarize myself with Spring Security, in particular migrating from Spring Security OAuth to Soring Security (as in the following example/guide https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide).
However, I am seeming to only get 403 Forbidden errors. I am accessing from Postman and am using my company's existing OAuth server. I am able to get a token from the auth server, so I know I have those credentials correct and I have verified what roles the OAuth user has.
I am using the following dependencies:
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-web'
This is the simple endpoint I am attempting to access:
#RestController
public class AppController
{
#GetMapping("/hello")
public String hello()
{
return "hello";
}
}
This is my application.yml file:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: <<company-website-here>>/uaa/oauth/token_keys
And this is my security configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/hello").hasRole("MY_ROLE")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
I can't seem to figure out why I seem to only get 403 errors. I have also tried adding #EnableWebSecurity to the security config class, but that didn't make a difference. Adding the auth server URL explicitly to the server and/or manually creating a JwtDecoder didn't do the trick either; it appears the url is being automatically picked up from the yml file, based on its property name.
I am trying to move away from using the org.springframework.security.oauth.boot dependency and ResourceServerConfigurerAdapter.
I had to add my own converter like so:
private static class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
private final Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter;
public JwtAuthenticationConverter()
{
this.jwtGrantedAuthoritiesConverter = jwt -> jwt
.getClaimAsStringList("authorities")
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public final AbstractAuthenticationToken convert(#NonNull Jwt jwt)
{
Collection<GrantedAuthority> authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
return new JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("client_id"));
}
}
Then had to add this to the main security config:
.jwtAuthenticationConverter(new JwtAuthenticationConverter());
There may be a couple of things happening.
As you're migrating to Spring Security 5, you may need to extract your authorities manually. Check this post and it's correct answer.
You are using hasRole function and this will append "ROLE_" before your authority/role. So if the role on your JWT token is not ROLE_JWT_ROLE you should use
hasTransaction.
Related
I am building an application where authentication is done by spring security for HTTP handlers, for HTTP I've disabled csrf protection, and now I want to disable csrf for spring web socket, but I can't figure out how to accomplish this, I've already tried many different approaches but no one seems to be working. If it is impossible to disable csrf for WebSocket how to get a csrf token? (I tried setting up the csrf endpoint to obtain a token but it is not work, and all tutorials I've found are outdated)
Thanks in advance!
web socket security config:
#Configuration
#EnableWebSocketSecurity
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(
MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages.anyMessage().permitAll();
return messages.build();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
security config:
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig {
#Autowired
private JwtFilter jwtFilter;
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity HTTP) throws Exception {
return http.addFilterBefore(jwtFilter, BasicAuthenticationFilter.class)
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/authenticate").permitAll()
.requestMatchers("/createchatroom").authenticated()
.requestMatchers("/public/*").permitAll()
.requestMatchers("/private/*").permitAll()
.requestMatchers("/ws/**").authenticated()
.requestMatchers("/register").permitAll()
.requestMatchers("/csrf").authenticated()
.requestMatchers("/addEmployeeToFavorites").hasAnyAuthority(EMPLOYEE.name(),
ADMIN.name())
.requestMatchers("/addChatRoomToFavorites")
.hasAnyAuthority(EMPLOYEE.name(), ADMIN.name())
.requestMatchers("/home").hasAnyAuthority(EMPLOYEE.name(), ADMIN.name()))
.build();
}
}
By default, Spring Security requires the CSRF token in any CONNECT message type. This ensures that only a site that has access to the CSRF token can connect. Since only the same origin can access the CSRF token, external domains are not allowed to make a connection.
Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
In Spring Security 5.8, this support has been refreshed to use the AuthorizationManager API.
To configure authorization using Java Configuration, simply include the #EnableWebSocketSecurity annotation and publish an AuthorizationManager<Message<?>> bean or in XML use the use-authorization-manager attribute. One way to do this is by using the AuthorizationManagerMessageMatcherRegistry to specify endpoint patterns like so:
#Configuration
#EnableWebSocketSecurity
public class WebSocketSecurityConfig {
#Bean
AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
messages
.simpDestMatchers("/user/**").authenticated()
return messages.build();
}
}
Any inbound CONNECT message requires a valid CSRF token to enforce the Same Origin Policy.
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request.
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in [websocket-authorization]
At this point, CSRF is not configurable when using #EnableWebSocketSecurity, though this will likely be added in a future release.
To disable CSRF, instead of using #EnableWebSocketSecurity, you can use XML support or add the Spring Security components yourself, like so:
Java
#Configuration
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
AuthorizationManager<Message<?>> myAuthorizationRules = AuthenticatedAuthorizationManager.authenticated();
AuthorizationChannelInterceptor authz = new AuthorizationChannelInterceptor(myAuthorizationRules);
AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(this.context);
authz.setAuthorizationEventPublisher(publisher);
registration.interceptors(new SecurityContextChannelInterceptor(), authz);
}
}
web.xml
<websocket-message-broker use-authorization-manager="true" same-origin-disabled="true">
<intercept-message pattern="/**" access="authenticated"/>
</websocket-message-broker>
On the other hand, if you are using the legacy-websocket-configuration and you want to allow other domains to access your site, you can disable Spring Security’s protection. For example, in Java Configuration you can use the following:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
...
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
References
WebSocket Security - Spring Security
I'm new to Spring boot and Spring Security. I have microservice project using Spring boot. And in my gateway app, I use OAuth2 for authentication. The authentication provider is from my organization and it is OIDC implementation.
I'm using oauth2 resource server to authenticate the bearer token, by configuring jwk-set-uri and jwk-set-uri properties.
spring-boot-starter-web => 2.6.7
spring-boot-starter-oauth2-resource-server => 2.6.7
spring-security => 5.6.3
application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://<org-auth-url>.com
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<org-auth-url>/<jwk-uri>
With just above configuration, the authentication works fine. So I have not added any Security Config class also. But for authorization and other processing like to get user data in Controller, I need the user information and AD group details.
I have the user information endpoint URL. And when I test it in postman client, the response contains user information along with AD groups.
How to get the User details for Authorization?
Ok.
You've already added the required uri. Good.
Now you need to add some configuration:
#Configuration
#EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration {
#Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
String jwkSetUri;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET,
///// more your requestMatchers /////
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
}
}
Now you should be able to receive jwt claims in your controllers with #AuthenticationPrincipal annotation.
#RestController
public class YourController {
#GetMapping("/")
public String doAnything(#AuthenticationPrincipal Jwt jwt) {
return jwt.getSubject();
}
}
Please add more info and I'll try to explain it better :-)
==== UPD ====
Really useful official manual on this.
Official code samples
I am trying to develop a spring-boot based rest API service with API documentation through Swagger UI. I want to enable basic authentication via the swagger UI so that the user can only run the API's once he/she authenticates using the Authorize button on swagger UI (by which a "authorization: Basic XYZ header is added to the API Call
At the front end (in the .json file for the Swagger UI I have added basic authentication for all the APIs using the following code (as per the documentation):
"securityDefinitions": {
"basic_auth": {
"type": "basic"
}
},
"security": [
{
"basic_auth": []
}
]
How should I implement the backend logic for the use case mentioned above (user can only run the API's once he/she authenticates using the Authorize button on swagger UI and it otherwise shows a 401 Error on running the API)
Some documentation or sample code for the same would be helpful
One option is to use the browser pop up authorization.
When you enable basic auth for your spring boot app, swagger ui will automatically use the browser's pop up window in order to use it for basic auth. This means that the browser will keep the credentials for making requests just like when you trying to access a secured GET endpoint until you close it.
Now, let's say you DON'T want to use the above and want swagger-ui for basic authentication as you say, you have to enable auth functionality on swagger-ui and optionally add security exception when accessing swagger-ui url.
To enable the basic auth functionality to swagger UI (with the "Authorize button" in UI) you have to set security Context and Scheme to your Swagger Docket (This is a simplified version):
#Configuration
#EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer{
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(basicAuthScheme()));
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(Arrays.asList(basicAuthReference()))
.forPaths(PathSelectors.ant("/api/v1/**"))
.build();
}
private SecurityScheme basicAuthScheme() {
return new BasicAuth("basicAuth");
}
private SecurityReference basicAuthReference() {
return new SecurityReference("basicAuth", new AuthorizationScope[0]);
}
}
This enables the authorization button in ui.
Now you probably want for your users to access the swagger-ui freely and use this button for authorization. To do this you have to exempt swagger for app's basic auth. Part of this configuration is Security config and you have to add following code:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/", "/csrf",
"/v2/api-docs",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**"
).permitAll()
.anyRequest().authenticated();
}
}
A similar problem I was facing was that when using springfox documentation with Swagger OAS 3.0, the "Authenticate" button would not appear on the swagger UI.
Turns out there was a bug created for this very issue-
https://github.com/springfox/springfox/issues/3518
The core of the problem-
Class BasicAuth is deprecated.
The solution as found in the bug report above is to use HttpAuthenticationScheme instead to define the SecurityScheme object.
The Docket configuration then looks like so-
return new Docket(DocumentationType.OAS_30)
.groupName("Your_Group_name")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.mypackage"))
.paths(PathSelectors.regex("/.*"))
.build().securitySchemes(Arrays.asList(HttpAuthenticationScheme.BASIC_AUTH_BUILDER.name("basicAuth").description("Basic authorization").build()))
.securityContexts(); //define security context for your app here
Use a following dependency in build.gradle to enable a security:
"org.springframework.boot:spring-boot-starter-security"
In application.properties you can define your own username and password using:
spring.security.user.name=user
spring.security.user.password=password
Those who want to basic auth only for endpoints should do everything what #Sifis wrote but need to change antMatchers as:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/",
"/v2/api-docs/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/swagger-ui/**",
"/swagger-ui.html").permitAll()
.anyRequest().authenticated();
}
}
I work with a web app that exposes a REST API to mobile apps. I upgraded my Spring Boot version from 1.5.3.RELEASE to 2.0.2.RELEASE and after fixing a few breaking changes I am facing one that I cannot solve.
I followed this Spring Boot 2.0 Migration Guide and Spring Boot Security 2.0 and also looked into Security changes in Spring Boot 2.0 M4.
The issue is that the app uses JWT authentication and there is an endpoint (/auth/login) accepts user credentials and generates a long-lived JWT in return.
There is a filter that examines the JWT token sent by the client and determines whether the client can access the requested resource.
Custom security config is like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfiguration {
#Configuration
#Order(1)
public class AuthenticationConfiguration extends WebSecurityConfigurerAdapter {
// Some dependencies omitted
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because JWT token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/version/**").permitAll()
// Some more antMatchers() lines omitted
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter(jwtTokenUtil);
}
}
#Configuration
#Order(2)
public class ClientVersionSupportConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(versionCheckingFilter())
.addPathPatterns("/**")
.excludePathPatterns("/error"); // Some more endpoints omitted
}
#Bean
public VersionCheckingInterceptor versionCheckingFilter() {
return new VersionCheckingInterceptor();
}
}
}
Note the .antMatchers("/auth/**").permitAll() line. /auth endpoints should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.
Before upgrading Spring Boot, it worked fine, now it is not working. Login attemps are rejected by the filter that checks the JWT. Looks like .permitAll() is not making the requests pass through. /version/** does not work either. Hitting it from the browser gives an error page.
I also tried to delete lines from the config until this remained:
httpSecurity
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
It did not help. Could you please help with restoring the original behavior?
Do you have a base path for you api, e.g. /api ?
The server.contextPath default Spring property name has changed to server.servlet.context-path.
So if you use a default base path for you api, you won't find the endpoints where you expect them. Unless you update the property ;)
This question already has answers here:
Is it possible to get an access_token from Spring OAuth2 server without client secret?
(4 answers)
Closed 5 years ago.
I have a Spring Security OAuth 2.0 based application, configured with a JDBC and LDAP. As per the OAuth 2.0 specification, client secret must.
When I generate token by using following URL it generates token and works fine:
/oauth/token?grant_type=password&client_secret=test&client_id=test&username=test&password=test
and when I try to generate token without client_secret it gives:
401: Unauthorized
error_description: "Bad User Credentials"
but I want to generate token without client_secret like:
/oauth/token?grant_type=password&username=test&password=test
securityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity( prePostEnabled = true )
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private static final int EMBEDDED_LDAP_SERVER_PORT = 33388;
#Autowired
private UserAuthenticationProvider userAuthenticationProvider;
#Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
#Autowired
private AuthTokenStore oAuthTokenStore;
#Autowired
private AuthDelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint;
#Override
#Qualifier("authenticationManagerBean")
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Arrays.asList((AuthenticationProvider) ldapAuthenticationProvider,userAuthenticationProvider));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(delegatingAuthenticationEntryPoint);
}
#Bean
public ResourceServerTokenServices tokenService() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(oAuthTokenStore);
tokenServices.setReuseRefreshToken(true);
return tokenServices;
}
Unfortunately there is no easy way around your problem. Spring security interprets the standard very strict:
This is a quote from the OAuth2 spec, RFC 6749, section 4.3.2 (Resource Owner Password Credentials Grant - Access Token Request):
If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.
For spring security the password grant always falls into this category. Section 3.2.1 requires client ID and client password.
Also the spring security documentation goes this way:
28.1.1 Authorization Server
Unless you want to change the authentication logic of spring security's OAuth2 (not recommended) you are stuck.
From my point of view there is no problem. Client ID and password costs you nothing and bring a little bit more security to your application.