Is it possible to use #RolesAllowed with Spring Boot 2.1.6.RELEASE and Keycloak 4.0.0.FINAL? - spring-boot

For me, it's possible to solve the roles with the application.properties file or the method below:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().anyRequest().authenticated();
}
I want to use the permissions with the annotation of #RolesAllowed. It's more secure, since you can not forget any endpoint. For me this is a big problem in
#GetMapping(value = "")
#RolesAllowed("note-reader")
public ResponseEntity<?> getNotes(#RequestParam(value = "page", defaultValue = "0") int page,
HttpServletResponse response, HttpServletRequest request) {
return new ResponseEntity<>(noteService.getNotes(page), HttpStatus.OK);
}
In the article https://rieckpil.de/howto-microprofile-jwt-authentication-with-keycloak-and-react/ they use it like this. But they don't use spring. Is there a way to solve it in this way or is the properties file the way to do it in spring?

The answer is: No, you should use spring method security.
Read more here: https://www.baeldung.com/spring-security-method-security

Related

Is possible ask for an acces token oauth2 just with refresh token in spring security? without basic authentication?

I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...

How do I get the right user principal with Spring-Boot and Oauth2?

I am practising Spring-Boot 2.2 + Spring Security 5 + Oauth2. Following a lot of examples I am hitting a wall here.
My spring security configuration is this:
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/css/**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login().userInfoEndpoint()
.userService(new DefaultOAuth2UserService());
}
and I have a controller with this method:
#GetMapping("/create")
public ModelAndView create(Principal principal) {
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}
in the Thymeleaf template I would call <span sec:authentication="name">User</span>, and it only returns a number.
in debug, authentication is org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken, and the Principal is a DefaultOidcUser, the name attribute is "sub", which is not a name but a number in google's oauth response.
DefaultOAuth2UserService is never called before my breakpoint hits in the controller.
Where did I take the wrong turn?
--edit--
In further debugging, I think the problem stems from OAuth2LoginAuthenticationFilter calling org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService which would be configurable by oidcUserService(oidcUserService())
To get current principal you can use #AuthenticationPrincipal annotation which resolves Authentication.getPrincipal() so you can add it as argument.
public ModelAndView create(#AuthenticationPrincipal Principal principal) {
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}
You can make use of the SecurityContextHolder.
public ModelAndView create() {
Object principal = SecurityContextHolder.getContext().getAuthentcation().getPrincipal();
log.debug(principal);
return new ModelAndView("create.html", "topicForm", new TopicForm());
}

Spring Boot - How to disable Keycloak?

I have a Spring Boot project with keycloak integrated. Now I want to disable keycloak for testing purposes.
I tried by adding keycloak.enabled=false to application.properties as mentioned in Keycloak documentation but it didnt work.
So how do I disable it?
Update 2022
Please follow this excellent guide on Baeldung.
For anyone who might have the same trouble, here is what I did.
I didn't disable Keycloak but I made a separate a Keycloak config file for testing purposes.
Here is my config file
#Profile("test")
#Configuration
#EnableWebSecurity
public class SecurityTestConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
http.headers().frameOptions().disable();
http.csrf().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken accessToken() {
AccessToken accessToken = new AccessToken();
accessToken.setSubject("abc");
accessToken.setName("Tester");
return accessToken;
}
}
Please note it is important to use this only in a test environment and therefore I have annotated the config as #Profile("test"). I have also added an AccessToken bean since some of the auditing features in my application depend on it.
It should work, but based on the last comment on the jira ticket for this, it seems it is not.
As the description state you could exclude the spring boot autoconfiguration for keycloak adding to your application.properties: spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
You need to exclude keycloak auto configuration. In order to do this just add this entry to your related spring configuration file, in your case application.properties.
spring.autoconfigure.exclude = org.keycloak.adapters.springboot.KeycloakAutoConfiguration
My Workaround:
1. Create a Custom-Filter and add it to the (Spring) Security-Chain in early position.
2. Create a flag in the application.yml (securityEnabled)
3. Query the flag in the Custom-Filter. If 'true' simply go on with the next filter by calling chain.doFilter(). If 'false' create a dummy Keycloak-Account set the roles you need and set it to the context.
4. By the way the roles are also outsourced to the application.yml
5. Skip the rest of the filters in the Security-Chain (so no keycloak-stuff is executed and the corresponding Authorization happend)
In Detail:
1. Class of Custom-Filter
public class CustomFilter extends OncePerRequestFilter {
#Value("${securityEnabled}")
private boolean securityEnabled;
#Value("${grantedRoles}")
private String[] grantedRoles;
#Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
if (!securityEnabled){
// Read roles from application.yml
Set<String> roles = Arrays.stream(grantedRoles)
.collect(Collectors.toCollection(HashSet::new));
// Dummy Keycloak-Account
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(null, null, null, null, null, null, null);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>("Dummy_Principal", session);
final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, principal.getKeycloakSecurityContext());
// Dummy Security Context
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(new KeycloakAuthenticationToken(account, false));
SecurityContextHolder.setContext(context);
// Skip the rest of the filters
req.getRequestDispatcher(req.getServletPath()).forward(req, res);
}
chain.doFilter(req, res);
}
}
2. Insert Custom-Filter in the http-Configuration of Spring-Security
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors()
.and()
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.addFilterAfter(CustomFilter(), CsrfFilter.class)
.authorizeRequests()
.anyRequest().permitAll();
}
Have a look at the default Filter-Chain after configuring Keycloak:
Filter-Chain
So it´s obvious to insert the Custom-Filter at position 5 to avoid the whole Keycloak-Magic.
I have used this workaround to defeat the method security and it´s #Secured-Annotation.
Updated answer for spring boot 2.5.6 and keycloak 16.1.0
Set this in your application.properties:
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakAutoConfiguration
(The autoconfig class name has changed since earlier answers)
The keycloak adapter dependency brings in the standard spring security autoconfig too, so if you want to disable both use this:
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakAutoConfiguration,org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

Modify Spring Security Config at Runtime

I am using the latest Spring Boot + Spring Boot Starter Security for a simple proxy application. The goal is to launch the application with a single route/method:
#RequestMapping(value = "/api/register",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<?> register(Registration registration) {
With a security configuration of:
#Override
protected void configure(HttpSecurity http) throws Exception {
this.http = http
.authorizeRequests()
.antMatchers("/api/register").hasAuthority(AuthorityConstants.ADMIN)
.and();
}
public HttpSecurity getHttpSecurity() {
return http;
}
The goal of the application would be to accept registration requests of the form:
{
"route":"/api/foo/bar",
"proxy_location": "http://back-end-server/path/to/resource",
"role": "some-authority"
}
And then the application would add an /api/foo/bar route with a pre-defined method that will proxy (forward) future requests to the backend service.
I know this is a little goofy, the real use-case involves websockets and dynamic creation of topics.
The issue I'm facing is that I cannot seem to update the security configuration after the SecurityConfigurer has completed.
In the code sample above I am caching the HttpSecurity object given to my SecurityConfigurer and then trying to use that object again to configure a new route:
#Inject
private SecurityConfigurer security;
#RequestMapping(value = "/api/register",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<?> allowGetAccounts(Registration registration) {
try {
security.getHttpSecurity()
.authorizeRequests()
.antMatchers(registration.getRoute()).hasAuthority(registration.getRole());
...
} catch (Exception e) {
log.error("Updating security failed!", e);
}
return new ResponseEntity<>(null, HttpStatus.OK);
}
Is there any way to update the security configuration dynamically during runtime?
Also, if anyone has any notes on creating websocket topics dynamically that would be appreciated too!
You have several options:
use antchMatcher("/some/path").access("#someBean.hasAccess(authentication)") . This allows you basically use any bean in your application context to apply the validation you need.
Use #PreAuthorize("#someBean.hasAccess(authentication)") on you RequestMapping annotated method. Same idea as before but as an interceptor on the endpoint itself.
Implement your own SecurityExpressionHandler and plug it into http.authorizeRequests().expressionHandler(...).
Implement your own Security filter that handles whatever you need.

How to get/set the principal and session attributes from Spring 4 stomp websocket methods

I'm doing experiments with Spring 4 websockets and stomp, and I have a hard time figuring out how to get/set the current user and other session attributes in a message handling method annotated with #MessageMapping.
The documentation says that the message handling methods can take a Principal as argument, and I found that the principal is retrieved by Spring by calling getUserPrincipal() on the native socket session, and then associated with the socket session, but I haven't found any way to easily customize this behavior, other than writing a servlet filter and wrap the original request into a wrapper returning the principal found in my cookie.
So my questions are:
How to manually set the principal to the socket session, when the client connects (I have this information thanks to a custom cookie, and I don't use Spring security)?
If 1 is not possible, how to add additional attributes to the socket session when the client connects?
How to access the socket session and its attributes from a message handling method?
Is there a way to access the login and passcode sent by the browser at connection time. They seem to be completely ignore by Spring and not accessible.
UPDATE: With Spring 4.1 it is possible to set the user on the handshake for #1 from above. Per the Spring documentation you can create a new class which extends DefaultHandshakeHandler and override the determineUser method. Additionally you can also create a security filter which sets the principal as well if you have a token. I have implemented the second one myself and I include some sample code for both below.
For #2 and #3 I do not think that it is possible still. For #4 Spring intentionally ignores these per the documentation here.
SAMPLE CODE FOR DefaultHandshakeHandler SUBCLASS:
#Configuration
#EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
public class MyHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// add your own code to determine the user
return null;
}
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());
}
}
SAMPLE CODE FOR SECURITY FILTER:
public class ApplicationSecurityTokenFilter extends GenericFilterBean {
private final static String AUTHENTICATION_PARAMETER = "authentication";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
// check to see if already authenticated before trying again
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
UsernamePasswordAuthenticationToken token = extractToken(request);
// dump token into security context (for authentication-provider to pick up)
if (token != null) { // if it exists
SecurityContextHolder.getContext().setAuthentication(token);
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
UsernamePasswordAuthenticationToken authenticationToken = null;
// do what you need to extract the information for a token
// in this example we assume a query string that has an authenticate
// parameter with a "user:password" string. A new UsernamePasswordAuthenticationToken
// is created and then normal authentication happens using this info.
// This is just a sample and I am sure there are more secure ways to do this.
if (request.getQueryString() != null) {
String[] pairs = request.getQueryString().split("&");
for (String pair : pairs) {
String[] pairTokens = pair.split("=");
if (pairTokens.length == 2) {
if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
String[] tokens = pairTokens[1].split(":");
if (tokens.length == 2) {
log.debug("Using credentials: " + pairTokens[1]);
authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
}
}
}
}
}
return authenticationToken;
}
}
// set up your web security for the area in question
#Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
.addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic() // leave this if you want non web browser clients to connect and add an auth header
.and()
.csrf().disable();
}
}
** NOTE: ** DO NOT declare your filter as a Bean. If you do then it will also be picked up (at least using Spring Boot) in the generic filters so it will fire on every request.
This is impossible for the time being (Spring 4.0). An issue has been opened (and considered) at Spring: https://jira.springsource.org/browse/SPR-11228

Resources