With my limited knowledge in spring security , I am trying to implement role-based authorisation to the REST endpoints of my spring boot application.My application authentication is done by an external system. I need to implement authorisation only in my context.I have multiple REST endpoints. These endpoints access need to be restricted according to the role that is coming in the jwt token.I need to know is there any way I can skip the authentication somehow in spring security. I prefer implementing this using annotation
#PreAuthorize("hasAnyRole('USER', 'ADMIN')") .
I have written a filter to extract the role details(Profile class contains the role info)
Optional.ofNullable(claims.get(USER_PROFILE).as(Profile.class));
But I'm not sure how to skip authentication and only implement authorisation. Can anyone provide me with a sample code or web links where I can refer for the solution?
read the chapter on "oauth2 resource server" in the spring security documentation
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html.
There it will tell you how to enable the handling of JWTs and then how to extract authorities manually from the JWT and map these to specific roles
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-authorization-extraction
Enabling the handling of JWTs in spring security
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
Extracting authorities from the jwt and mapping these to roles
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
These are just examples, they will not solve your problem, but if you read the docs, you will probably figure it out.
Here is a link to a github project with a full implementation and a blog post
https://github.com/Tandolf/spring-security-jwt-demo
Related
I have been working in Java for couple of years and recently switched to C# .NET. One of the common scenarios I was dealing with as backend developer was building secured REST API. Each request to the API contains Authorization header with Bearer access token. We use AWS Cognito as an external authorization server.
In Spring Boot, this was fairly easy task:
use spring-boot-starter-oauth2-resource-server
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
add security configuration
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf(CsrfConfigurer::disable)
.sessionManagement(SessionManagementConfigurer::disable)
.authorizeRequests(c -> c.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt();
return http.build();
}
define issuer-uri and jwk-set-uri properties
spring.security.oauth2.resourceserver.jwt.issuer-uri: ...
spring.security.oauth2.resourceserver.jwt.jwk-set-uri: ...
Now with this fairly generic configuration, all endpoints are secured.
However, I am unable to find the way to implement the same thing in C# .NET. All the tutorials and articles I found followed different approach, all of them bounding the solution with user management. What I am looking for is a pure resource server. Any help or pointing in the right direction would be appreciated.
Eventually I managed to come up with the solution, leaving it here as it may save someone else's time as well. Picked up Asgardeo this time as the external authorization server. Solution was tested in .NET 6.0:
add authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Audience = "<client_id>";
options.MetadataAddress = "https://api.asgardeo.io/t/<app_name>/oauth2/token/.well-known/openid-configuration";
});
use authentication (authentication, authorization and controller mapping must be in correct order)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
add secured endpoint to controller
[HttpGet]
[Route("Private")]
[Authorize]
public IActionResult Private()
{
return Ok(new
{
Message = "Hello from a private endpoint."
});
}
This solution works for applications without client secret.
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
What are the very basics of Spring Security i.e. how Spring sets up security internally. What are all the beans involved that are to be provided for Spring Security to work out-of-the-box?
I shall start first by explaining, how to bring in Spring Security into your application.
Just add below dependency to your application. Now, when you run your application the spring security is implemented by default. (As of April 2021, version might change in future)
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.4.5</version>
</dependency>
Closely looking at the console, you will see a password generated for default user: user. The password is a hash that you need to use.
When you access any URL from your application now, you will be restricted from Postman. From your browser, you will see a login page where you need to enter this username and password and you will be through to your URL. That sets up the inbuilt Spring Security.
But what is happening under the hood?
I shall answer it by reminding you of Servlets and Filters and DispatcherServlet in Spring.
DispatcherServlet is the very basic of Spring MVC and it forwards the requests to your controllers. Basically, DispatcherServlet is also a servlet.
I can create a chain of filters before DispatcherServlet and check my request for Authentication and Authorization before forwarding the request to hit my DispatcherServlet and then my controllers. This way, I can bring in Security to my application. This is exactly what the Spring Security does.
The below link very delicately highlights all the filters that are there before DispatcherServlet and what is the importance of those Filters. Please refer the link below:
How Spring Security Filter Chain works
Now, we need to understand what authentication and authorization is:
Authentication- Anyone using your application needs to have some info and you need to verify that user’s username, password to allow him to access your application. If his username or password is wrong, that means he is not authenticated.
Authorization- Once the user is authenticated, there might be some URLs of your application that should only be allowed to admin users and not normal users. This is called authorizing a user to access some parts of your application based on his role.
Let us look at some important Spring’s Filter in Filter Chain:
• BasicAuthenticationFilter: Tries to find a Basic Auth HTTP Header on the request and if found, tries to authenticate the user with the header’s username and password.
• UsernamePasswordAuthenticationFilter: Tries to find a username/password request parameter/POST body and if found, tries to authenticate the user with those values.
• DefaultLoginPageGeneratingFilter: Generates a login page for you, if you don’t explicitly disable that feature. THIS filter is why you get a default login page when enabling Spring Security.
• DefaultLogoutPageGeneratingFilter: Generates a logout page for you, if you don’t explicitly disable that feature.
• FilterSecurityInterceptor: Does your authorization.
These filters, by default, are providing you a login page which you saw on your browser. Also, they provide a logout page, ability to login with Basic Auth or Form Logins, as well as protecting against CSRF attacks.
Remember, the login page at the beginning just after adding Spring Security to your pom.xml. That is happening because of the below class:
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
}
This WebSecurityConfigurerAdapter class is what we extend and we override its configure method. As per above, all the requests need to do basic authentication via form login method. This login page is the default provided by Spring that we saw when we accessed our URL.
Now, next question arises, what if we want to do this configuration ourselves? The below topic discusses exactly that:
How to configure Spring Security?
To configure Spring Security, we need to have a #Configuration, #EnableWebSecurity class which extends WebSecurityConfigurerAdapter class.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.httpBasic();
}
}
You must do above the mentioned configurations. Now, you can do your specific security configuration i.e. which all URLs are allowed, which need to be authenticated, what are the types of authentication the application will perform and what are the roles that are allowed on specific URLs.
So, basically, all your authentication and authorization information is configured here. Other configuration regarding CORS, CSRF and other exploits is also done here, but that is out of the scope of the basics.
In the example above, all requests going to / and /home are allowed to any user i.e. anyone can access them and get response but the other requests need to be authenticated. Also, we have allowed form login i.e. when any request apart from / and /home is accessed, the user will be presented with a login page where he will input his username and password and that username/password will be authenticated using basic authentication i.e. sending in an HTTP Basic Auth Header to authenticate.
Till now, we have added Spring Security, protected our URLs, configured Spring Security. But, how will we check the username and password to be authenticated? The below discusses this:
You need to specify some #Beans to get Spring Security working. Why some beans are needed?
Because Spring Container needs these beans to implement security under the hood.
You need to provide these two beans – UserDetailsService & PasswordEncoder.
UserDetailsService – This is responsible for providing your user to the Spring container. The user can be present either in your DB, memory, anywhere. Ex: It can be stored in User table with username, password, roles and other columns.
#Bean
public UserDetailsService userDetailsService() {
return new MyUserDetailsService();
}
Above, we are providing our custom MyUserDetailsService which has to be a UserDetailsService child for Spring container to identify its purpose. Below is the sample implementation:
public class MyDatabaseUserDetailsService implements UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Load the user from the users table by username. If not found, throw UsernameNotFoundException.
// Convert/wrap the user to a UserDetails object and return it.
return someUserDetails;
}
}
public interface UserDetails extends Serializable {
String getUsername();
String getPassword();
// isAccountNonExpired,isAccountNonLocked,
// isCredentialsNonExpired,isEnabled
}
You see, UserDetailsService shall provide the container with UserDetails object.
By default, Spring provides these implementations of UserDetailsService:
1. JdbcUserDetailsManager- which is a JDBC based UserDetailsService. You can configure it to match your user table/column structure.
2. InMemoryUserDetailsManager- which keeps all userdetails in memory. This is generally used for testing purposes.
3. org.springframework.security.core.userdetail.User– This is what is used mostly in custom applications. You can extend this User class on your custom implementation for your user object.
Now, as per above if any request arrives and needs to be authenticated, then since we have UserDetailsService in place, we will get the user from the UserDetails object returned by UserDetailsService for the user who has sent the request and can authenticate his sent username/password with the one received from our UserDetailsService.
This way, the user is authenticated.
Note: The password received from user is automatically hashed. So, if we do not have the hash representation of password from our UserDetailsService, it will fail even when the password is correct.
To prevent this, we provide PasswordEncoder bean to our container which will apply the hashing algorithm specified by the PasswordEncoder on the password in UserDetails object and make a hash for it. Then, it checks both the hashed passwords and authenticates or fails a user.
PasswordEncoder- This provides a hash of your password for security purposes. Why? You cannot/should not deal with plain passwords. That beats the very purpose of Spring Security. Better, hash it with any algorithm.
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
Now, you can autowire this PasswordEncoder anywhere in your application.
AuthenticationProvider-
In some cases, we do not have access to the user’s password but some other third party stores our user's information in some fancy way.
In those cases, we need to provide AuthenticationProvider beans to our Spring container. Once container has this object, it will try to authenticate with the implementation we have provided to authenticate with that third party which will give us a UserDetails object or any other object from which we can obtain our UserDetails object.
Once, this is obtained, that means we are authenticated and we will send back a UsernamePasswordAuthenticationToken with our username, password and authorities/roles. If it is not obtained, we can throw an exception.
#Bean
public AuthenticationProvider authenticationProvider() {
return new MyAuthenticationProvider();
}
An AuthenticationProvider consists primarily of one method and a basic implementation could look like this:
public class MyAuthenticationProvider implements AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String username = authentication.getPrincipal().toString();
String password = authentication.getCredentials().toString();
User user = callThirdPartyService(username, password);
if (user == null) {
throw new AuthenticationException("Incorrect username/password");
}
return new UserNamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
}
}
Thats all there is to Spring Security basics or under the hood functionality and how we can leverage these to customize our security implementation. You can find examples anywhere. More advanced topics such as JWT, Oauth2 implementation, CSRF prevention, CORS allowance are beyond the scope.
Java + Spring (and Spring Security) here, interested in implementing a JWT-based auth mechanism for my web service using bearer tokens. My understanding of the proper way of using Spring Security for authentication and authorization is through the use of provided (or custom) filters as follows:
you specify which URLs in your app are authenticated (and thus require authenticated requests to access)
this is typically done in an #EnableWebSecurity-annotated web security class that extends WebSecurityConfigurerAdapter
for any unauthenticated URLs, no filters should block access to the resources being requested
an authentication filter effectively provides a "sign in" endpoint
request clients should hit this signin endpoint (authn filter) initially to obtain an auth token that can be used for making subsequent API calls
this filter should receive a type of "sign in request" object that contains a principal (e.g. username) and credential (e.g. password)
this authn filter should use the principal/credential contained in the sign in request to determine if they represents a valid user in the system
if so, an auth token (JWT, etc.) is generated and sent back to the requesters in the response somehow
else, if the principal/credential don't match a valid user in the system, an error response is returned and authentication fails
for authenticated URLs, a verification filter verifies that the request contains an auth token and that the auth token is valid (was signed correctly, contains user information such as JWT claims, is not expired, etc.)
if the auth token is valid, the request continues on to the authorization filter (see below)
else if the auth token is not valid, verification fails and the filter sends an error response back to the client
finally, an authorization filter verifies that the user associated with the valid auth token has the ability/permission to make such a request
if they do, then the request is allowed to continue on to whatever resources/controller was written to handle it, and that resource/controller provides the response back to the requester
if they don't, an error response is returned to the client
ideally the logic (code) inside this authz filter would have access to the permission annotations added to the resource method, so that I can add endpoints and specify permissions on them without having to modify the code of the authz filter
So to begin with, if anything I have stated above is a Spring Security (or web security in general) anti-pattern or is misled, please begin by providing course correction and steering me in the right direction!
Assuming I'm more or less understanding the "auth flow" above correctly...
Are there any specific Spring Security filters that take care of all of this for me already, or that can be extended and have a few methods overridden to behave this way? Or anything that comes really close? Looking at the list of authentication-specific Spring Security filters I see:
UsernamePasswordAuthenticationFilter -> looks like a decent candidate for the authn filter but expects a username and password parameter on the query string which is strange to me, and most importantly, does not generate a JWT
CasAuthenticationFilter -> looks like its used for CAS-based SSO and is not appropriate for use in non-SSO contexts
BasicAuthenticationFilter -> for HTTP basic authentication-based auth, not appropriate for more sophisticated setups
As for token verification and authorization, I (much to my surprise) don't see anything in the Spring Security landscape that could qualify.
Unless anyone knows of JWT-specific filters that I can use or subclass easily, I think I need to implement my own custom filters, in which case I'm wondering how to conigure Spring Security to use them and not use any of these other authentication filters (such as UsernamePasswordAuthenticationFilter) as part of the filter chain.
As I understand it, you want to:
Authenticate users via a username and password and respond with a JWT
On subsequent requests, authenticate users using that JWT
username/password -> JWT isn't an established authentication mechanism on its own, which is why Spring Security doesn't yet have direct support.
You can get it on your own pretty easily, though.
First, create a /token endpoint that produces a JWT:
#RestController
public class TokenController {
#Value("${jwt.private.key}")
RSAPrivateKey key;
#PostMapping("/token")
public String token(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
// #formatter:off
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("self")
.issueTime(new Date(now.toEpochMilli()))
.expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
.subject(authentication.getName())
.claim("scope", scope)
.build();
// #formatter:on
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
SignedJWT jwt = new SignedJWT(header, claims);
return sign(jwt).serialize();
}
SignedJWT sign(SignedJWT jwt) {
try {
jwt.sign(new RSASSASigner(this.key));
return jwt;
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
}
Second, configure Spring Security to allow HTTP Basic (for the /token endpoint) and JWT (for the rest):
#Configuration
public class RestConfig extends WebSecurityConfigurerAdapter {
#Value("${jwt.public.key}")
RSAPublicKey key;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
// #formatter:on
}
#Bean
UserDetailsService users() {
// #formatter:off
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password("{noop}password")
.authorities("app")
.build());
// #formatter:on
}
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
}
I think there's appetite to add support for something like this in spring-authorization-server to reduce the /token boilerplate, if you're interested in contributing your efforts!
I have a Spring Boot web application that is run on a Tomcat application server and authenticates against a third party IdP.
We currently do role based authentication in a number of our apps using <security-role> and <security-constraint> in a web.xml, and it works properly.
Now, attempting to use Spring Security, I have added the following configuration class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
String[] publicPaths = /*get public paths from properties*/
String[] authorizedPaths = /*get authorized paths from properties*/
String[] authorizedRoles = /*get authorized roles from properties*/
http.csrf().disable()
.jee()
.mappableAuthorities(authorizedRoles)
.and()
.authorizeRequests()
.antMatchers(publicPaths).permitAll()
.antMatchers(authorizedPaths).hasAnyRole(authorizedRoles)
.and()
.logout().disable()
;
}
}
The authorizedRoles in the config above are roles that are authorized to access this application. However, there are other manual checks in the app that just call HttpServletRequest.isUserInRole() to determine if a user has a certain role. Before using Spring Security, that call would return true if that user had that role in the original request. After adding Spring Boot, that call only returns true if the role is one of those passed to .mappableAuthorities() in the example above. The roles that are checked via HttpServletRequest.isUserInRole() are stored in a database and can be updated often, so passing them to .mappableAuthorities() when the application loads is not feasible.
So, to get to the point of my question, it seems like Spring Security is modifying the original HttpServletRequest and taking out any roles that are not contained in the authorizedRoles passed to .mappableAuthorities().
Is there a way to avoid this behavior, or possibly pass some kind of wildcard to .mappableAuthorities(), so that you don't have to know all roles on application startup for them to be accessible via a call to HttpServletRequest.isUserInRole()? I've been looking at Spring Security documentation for hours and haven't found anything.
You can see only mapped roles, because SecurityContextHolderAwareRequestFilter wraps the HttpServletRequest:
A Filter which populates the ServletRequest with a request wrapper which implements the servlet API security methods.
It uses SecurityContextHolderAwareRequestWrapper to implement the servlet API security methods:
A Spring Security-aware HttpServletRequestWrapper, which uses the SecurityContext-defined Authentication object to implement the servlet API security methods:
getUserPrincipal()
isUserInRole(String)
HttpServletRequestWrapper.getRemoteUser().
To customize the roles mapping see J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource:
Implementation of AuthenticationDetailsSource which converts the user's J2EE roles (as obtained by calling HttpServletRequest.isUserInRole(String)) into GrantedAuthoritys and stores these in the authentication details object.
It uses a MappableAttributesRetriever to get the mappable roles:
Interface to be implemented by classes that can retrieve a list of mappable security attribute strings (for example the list of all available J2EE roles in a web or EJB application).
You could write your own MappableAttributesRetriever which loads the mappable roles from your database.
Or you can use WebXmlMappableAttributesRetriever, which retrieves the roles from web.xml:
This MappableAttributesRetriever implementation reads the list of defined J2EE roles from a web.xml file and returns these from getMappableAttributes().