Using spring-webmvc and spring-security-web of version 3.2, I'd like to return different views depending on the user role (or whether user is authenticated or not), so that for a "/" request a user of role ANONYMOUS (or not authenticated user) gets the welcome page and a user of role USER gets the home page.
My current approach is doing this with a regular controller:
#Controller
public class WelcomeCtrl {
#RequestMapping("/")
public String welcome(Principal principal) {
if (userAuthenticated(principal)) {
return "redirect:home";
}
return "welcome";
}
private boolean userAuthenticated(Principal principal) {
return principal != null && principal instanceof Authentication
&& hasUserRole((Authentication) principal);
}
private boolean hasUserRole(Authentication principal) {
Collection<? extends GrantedAuthority> authorities = (principal)
.getAuthorities();
return Iterables.contains(Collections2.transform(authorities,
new Function<GrantedAuthority, String>() {
#Override
public String apply(GrantedAuthority authority) {
return authority.getAuthority();
}
}), "ROLE_USER");
}
}
However, I don't really like it because I feel that this redirection should be done with spring security (am I wrong?). Do you know any way of doing this with Spring Security configuration? My current config is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/welcome").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.defaultSuccessUrl("/home").permitAll()
.and().logout().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder)
throws Exception {
authManagerBuilder.inMemoryAuthentication().withUser("user")
.password("password").roles("USER");
}
}
According to me, this is the best way to use spring security configuration latest in the market.
General support for Java Configuration was added to Spring framework in Spring 3.1. Since Spring Security 3.2 there has been Spring Security Java Configuration support which enables users to easily configure Spring Security without the use of any XML.
If you are familiar with the Security Namespace Configuration then you should find quite a few similarities between it and the Security Java Configuration support and you can use Security Namespace Configuration as well which suites you
There are many new things added to the spring security 3.2 you can use that
like
1). Java Configuration -- better for those who are more familiar to read the jar files to apply the configuration methods.
2). Concurrency Support -- In most environments, Security is stored on a per Thread basis. This means that when work is done on a new Thread, the SecurityContext is lost. Spring Security provides some infrastructure to help make this much easier for users. Spring Security provides low level abstractions for working with Spring Security in multi threaded environments. In fact, this is what Spring Security builds on to integration with AsyncContext.start(Runnable) and Spring MVC Async Integration.
3). CSRF Attacks :- Assume that your bank’s website provides a form that allows transferring money from the currently logged in user to another bank account. For example, the HTTP request might look like:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.abc.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
Now pretend you authenticate to your bank’s website and then, without logging out, visit an evil website. The evil website contains an HTML page with the following form:
<form action="https://bank.example.com/transfer" method="post">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="ohh you Win Money!"/>
</form>
You like to win money, so you click on the submit button. In the process, you have unintentionally transferred $100 to a malicious user. This happens because, while the evil website cannot see your cookies, the cookies associated with your bank are still sent along with the request.
such type of the attacks can easily be stopped by using the spring security 3.2, as this new feature Cross Site Request Forgery (CSRF) Protection in spring security generate some token which are matched at the payment gateway side.
and many more advantages......................
Related
So I have an Angular frontend application, and a Spring backend. Currently, it seems that I have a cookie of JSessionId on my application (which I receive only on login, and not on register, for whatever reason)
(cookies)
I assume it sends those cookies back to the server. (though that's only an assumption)
Now, when I am making a request to the protected server, the only thing I get is this "Please login" popup.
Login popup
When I log in, my UserService logs a user with such details:
UsernamePasswordAuthenticationToken [Principal=User(userId=1, name=Maksym Riabov, username=MRiabov, password={bcrypt}$2a$10$W0XJRQdfxV5XXORkr2bTluIHvFetIVBzmVp51l39T5zLCQk12RV1i, company=null, enabled=true), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ADMIN]]
And what I've noticed is that the sessionId is null there. Why could that be?
To answer some of the questions forward:
Yes, I've pasted {withCredentials: true} to every request. (specific to Angular)
Yes, I've read documentation - I've even tried pasting all the code from it and it seems that it didn't work.
My login controller:
#GetMapping("/login")
public ResponseEntity<String> login() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.ok("123123");
}
#PostMapping("/register")
public ResponseEntity<Map<String, String>> register(#RequestBody UserRegisterDto userDto) {
//todo check if name taken
User user = userMapper.toEntity2(userDto);
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setEnabled(true);
//todo remove
Authority authority = authorityRepository.save(new Authority("ADMIN"));
user.setAuthorities(Set.of(authority));
//todo REMOVE!!!!
User savedUser = userRepository.save(user);
System.out.println("registration works!");
return ResponseEntity.ok(Map.of("result",authority.getAuthority().getAuthority()));
}
Now, I am sending a request to the backend (which puts the popup above) like this one:
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/create")
public ResponseEntity<OnboardingPathDto> createOnboardingPath() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// erased a bit of code here
return ResponseEntity.ok().build();
And as you see I have a method security, which throws the request for auth.
And, the cherry at the top:
#Component
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
http
.csrf().disable().cors().disable()
.authorizeHttpRequests()
.anyRequest().permitAll() //todo this is unsafe
.and().sessionManagement(session -> session.
sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1))//to force only one session per user
//here I tried sessionManagement to do something, but did it do something?
.rememberMe((rememberMe) -> rememberMe.userDetailsService(userDetailsService))
.httpBasic();
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
return new ProviderManager(daoAuthenticationProvider);
}
#Bean
public DaoAuthenticationProvider prov(PasswordEncoder passwordEncoder, UserDetailsService userDetailService) throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailService);
return daoAuthenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {//to force only one session per user
return new HttpSessionEventPublisher();
}
I've read through the Spring Security documentation far and wide, and even have taken a course in it, but I still couldn't manage to get it working.
So, what I'm struggling with:
Why can't Spring authenticate through the session even though it is configured to do so? Where is my error?
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.
You are right, this is a bad idea. For sessions in an application running in a browser, only use cookies with those two flag raised (value=true):
secure (exchanged only over https)
http-only (hidden from Javascript).
This means that cookies should not be accessible to the Angular code but automatically set by the browser before sending requests to the backend.
You should also implement CSRF protection (which is the default in spring-security).
Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.
Good idea. This is much better for security, user experience (SSO), and developper experience: most OIDC providers, either on premise (like Keycloak), or in the cloud (like Auth0, Cognito, and many others), already provide with login forms (including Multi Factor Authentication), user registration, profile edition, administration screens (like clients declaration, user roles assignement, etc.). For that:
configure your Spring REST API as a resource-server. I have written tutorials for this there
configure your Angular app either as:
an OAuth2 client. My favorite certified lib for Angular is angular-auth-oidc-client
a BFF client. Backend For Frontend is a pattern where a server-side middleware serves as the only OAuth2 client to hide tokens from the browser. Angular app won't be OAuth2: it will be secured with sessions (haha! your devil is back ;-), the middleware (something like spring-cloud-gateway with tokenRelay filter) will keep this session, associate tokens to it and replace sessions with tokens before forwarding requests to resource-server. Tutorial there.
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.
I have a Spring boot 2 application (rest API) and use the Springfox Swagger 2 library, including the UI library. When I open the swagger interface found at http://localhost:8080/swagger-ui.html everything works as expected, but two requests are made that give a 404 result in logger:
http://localhost:8080/ (nothing is mapped to the root of my app)
http://localhost:8080/csfr (this mapping also doesn't exist, but I know it stands
for 'cross site forged request')
Apparently Swagger does this because it 'supports' some kind of csfr token checking as explained here. There is an investigation going on for some months now whether these 404 calls can be configured so I'm now pondering implementing the endpoint instead. I'm failing to find information on what to atually immplement. What kind of header/token is swagger expecting, and what will it do with the information in it? Can I use this to make my app (or the swagger endpoint) more secure or accesible? In short: what is the point :)?
Let me answer your questions one by one.
Why are the request made to http://localhost:8080/ and
http://localhost:8080/csrf?
This is because, Springfox Swagger has by default enabled support for CSRF. What this does is, whenever you try to access any swagger endpoints in your application, it checks for the CSRF token in the below order and attaches it to the request header.
CSRF token within your meta tags served at /
Endpoint /csrf
CSRF token within your cookie
The reason Springfox Swagger attaches the CSRF token is, in case your application has CSRF protection enabled, the requests to the swagger endpoints would fail in case they don't have the CSRF token as part of the header.
What kind of header/token is swagger expecting, and what will it do with the information in it?
As I told earlier, swagger is expecting a CSRF token and it'll attach it to the header of the request when you try to access any swagger endpoints.
Can I use this to make my app (or the swagger endpoint) more secure or accessible?
Enabling CSRF protection within your app would make your app secure from CSRF attacks and not necessarily just by providing the CSRF token to swagger to attach to the header. In case you have enabled CSRF protection within your app, you must provide the CSRF token by any of the above 3 means to access any swagger endpoints within your app. You can read about the use of enabling CSRF protection here.
I'm failing to find information on what to actually implement
There is no use of implementing the CSRF token provision for swagger if you have not enabled CSRF protection within your app, as it would just be redundant. But in case you want to implement the CSRF token provision for swagger you can do it by either of the 3 methods below:
1) CSRF token within your meta tags served at /
<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
</head>
This is in case you are using any templating mechanism like JSP, thymeleaf etc.
2) Endpoint /csrf
Define an endpoint /csrf to provide the CSRF token.
#RequestMapping("/csrf")
public CsrfToken csrf() {
//logic to return the CSRF token
}
3) CSRF token within your cookie
The default cookie name searched for is XSRF-TOKEN, and the default header name returned is X-XSRF-TOKEN. Spring security provides a way of storing the CSRF token in the cookie as required by swagger with the configuration below
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Implementing either of the above 3 would provide swagger with the CSRF token to attach to the request header.
The reference to the above is from the GitHub PR which provided the CSRF support to Springfox swagger and also Spring security documentation which I have linked earlier.
There is currently an open issue regarding the default enabled CSRF support here, and couple of open PRs with the fix #2639 and #2706.
Depending on 2) Endpoint /csrf
In my case I disabled WebSecurity until yet and also get the 404 Error Code for /csrf. I fixed that with a simple Controller as mentioned above. Here is my Controller:
#Controller
public class CSRFController {
#RequestMapping(value = "/csrf", method = RequestMethod.GET, produces = "application/json;charset=UTF-8")
public ResponseEntity<CsrfToken> getToken(final HttpServletRequest request) {
return ResponseEntity.ok().body(new HttpSessionCsrfTokenRepository().generateToken(request));
}
}
If you use that, you need to add the maven dependency for web security:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
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().
I am writing an LTI application using Spring boot. LTI applications are basically a plug-in for a learning management system (in this case Canvas) which work by sending an Oauth1 signed POST to my server. The result of this request is displayed to the user inside of an iframe. There is a pre-shared key and secret that the LMS uses to sign the request. If the signature on the POST checks out, I have an authenticated user. I have this part working, partially based on this question.
During the initial request (which comes to the URL "/launch") I can call SecurityContextHolder.getContext().getAuthentication() and use this without problems. My problem is when the user makes another request, say for a picture or by clicking on a link in my content, the SecurityContext object isn't following them. I'm pretty sure I'm not setting up the Spring security filter chain correctly so the SecurityContextPersistenceFilter isn't being hit on subsequent requests. At the end of the day, SecurityContextHolder.getContext().getAuthentication() returns null.
The OAuth signature verification happens in a WebSecurityConfigurerAdapter like so: (again, based on this)
#Configuration
public static class OAuthSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//spring auto-wiring to set up the
//zeroLeggedOauthProviderProcessingFilter (see linked question)
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/launch")
.addFilterBefore(zeroLeggedOAuthProviderProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}
So this works and creates an authenticated principal and everything. But due to that antMatcher, it only applies to the /launch path.
It seems like it should be simple to add another security configurer adapter that will ensure that all other paths in the application are protected by an authenticated session and in so doing would cause the SecurityContext associated with this user to become available but I have been unable to come up with the magic sauce. The documentation focuses more on standard login form based authentication setups. I'm also kind of new to Spring in general so I'm clearly missing something. I tried this but it causes all other requests to return a 403:
#Configuration
public static class SessionSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}