Spring Security 3.2 Can't Logout - spring-security

I have a Spring Security java configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
...
.logout()
.permitAll();
}
And according to the document (http://docs.spring.io/spring-security/site/docs/3.2.0.RELEASE/reference/htmlsingle/), the logout URL is the same as the previous version as j_spring_security_logout (Interesting to know any other URLs in the login form have been changed). The URL doesn't work for me.
Anything missing?

Using Spring 3.2 and Java Config, they have changed the default URLs/field names to prevent info leakage (people being able to work out you are running a spring app by looking at the field names/URLs - it touches upon the change briefly here (although this only mentions the username/password/etc - not explicitly the logout, but I assume it has been changed for the same reasoning): http://docs.spring.io/spring-security/site/docs/3.2.0.RELEASE/reference/htmlsingle/#jc-httpsecurity.
If you update your link to post to /logout it should work.
If you look at the source (click through the Java code config) of the classes you will see the logout() method called from your config is on HttpSecurity.class and that applies the class LogoutConfigurer.class - you can see here the default matcher that is now being applied :
public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<LogoutConfigurer<H>,H> {
private List<LogoutHandler> logoutHandlers = new ArrayList<LogoutHandler>();
private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
private String logoutSuccessUrl = "/login?logout";
private LogoutSuccessHandler logoutSuccessHandler;
private RequestMatcher logoutRequestMatcher = new AntPathRequestMatcher("/logout", "POST");
(also worth noting, the POST method - plus, if you have not disabled CSRF protection, then you will need to provide the CSRF field as well - as for any post)

Related

Spring security with vaadin combined with thymeleaf

The spring security annotations are ignored on the Vaadin views; I have a #DenyAll at the class level but the view is rendered anyway.
The project combines thymeleaf and vaadin within spring boot. The first for a fast rendering of a large HTML with command and events over a websocket, the latter for ease of developing the administrative screens. Vaadin is setup under "/vdn/", spring MVC with thymeleaf under "/".
Spring security works correctly in thymeleaf; login, logout, and the sec:authorize correctly hides or shows parts of the generated HTML. But the security tag on the Vaadin view is ignored.
#Route("/")
#StyleSheet("context://../vaadin.css")
#DenyAll
public class MainView extends AppLayout {
Based on the documentation (https://vaadin.com/docs/latest/security/enabling-security) if no annotation is present the view should not be shown at all, it however is. So somehow Vaadin is not picking up the Spring security. Any suggestions what is missing?
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Autowired
private DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder()) //
.dataSource(dataSource) //
.usersByUsernameQuery("select username, password, enabled from person where username=?") //
.authoritiesByUsernameQuery("select username, role from person where username=?") //
;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests() //
.anyRequest().authenticated() //
.and() //
.formLogin() //
.and() //
.csrf().disable() // needed for vaadin https://tutorialmeta.com/question/server-connection-lost-after-successful-login-with-spring-security
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
;
return http.build();
}
}
As mentioned in the comments as well, in order for the Vaadin View-Based security to work, it should be enabled first by extending your security configuration class from VaadinWebSecurity (for V23.2+) or VaadinWebSecurityConfigurerAdapter (older versions). You can refer to the documentation here: https://vaadin.com/docs/latest/security/enabling-security/#security-configuration-class
When extending from either of the above classes, if you are overriding the void configure(HttpSecurity http) throws Exception method (which is needed almost always), do not forget to call super.configure(http); in the correct order mentioned that mentioned in the documentation. This is important as the viewAccessChecker bean is enabled through this call, and this is what you need to have the View-Based security work.
Note: You probably have seen the chain of calls on http.authorizeRequests() (which is of type ExpressionInterceptUrlRegistry) in many tutorials and documentations. Once the .anyRequest().PermitAll() or some similar method is called on it, it does not accept any more configuration of those pattern matching configurations, so it is important to configure your custom pattern matching configs before the call to super.configure(http); (as shown in the mentioned documentation).
Finally, setting the login form which is done in the documentation via calling setLoginView(http, LoginView.class); is an important step, as not only does it introduce your custom login view to the viewAccessChecker bean, but, it also enables Spring Security's form-based login feature which is needed for the view-based security mechanism to work properly.
If you have all the steps mentioned above in order, then those access annotations such as #DenyAll or #RolesAllowed are taken into account and you can expect them to work as documented. If you still have problems enabling it, please provide a Minimal, Reproducible Example that isolates your problem so that the community can help more effectively.

Single resource server with multiple authorisation servers, one for each tenant

I am working on a Spring Boot application, which is basically a resource server. As of now, my application has one tenant, which gets authenticated with an authorization server, external to my application.
In order to achieve the same, as of now, I have made the following changes in my application:
config changes are as following:
spring.security.oauth2.client.registration.tenant1.client-id=abcd
spring.security.oauth2.client.registration.tenant1.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant1.authorization-grant-type=authorization_code
myapp.oauth2.path=https://external.authorization.server/services/oauth2/
spring.security.oauth2.client.provider.tenant1.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant1.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant1.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant1.user-name-attribute=name
As of now, I am fetching client secrets from Vault, so I had to define the OAuth2 configuration as follows:
#EnableConfigurationProperties(OAuth2ClientProperties.class)
#Conditional(ClientsConfiguredCondition.class)
#Configuration
public class OAuth2Configuration {
static final String OAUTH2_CLIENT_SECRET_KEY = "oauth2_client_secret";
private static final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
private static final String OAUTH2_REGISTRATION_MISSING =
"oAuth2 registration properties are missing";
private final ApplicationSecretProvider applicationSecretProvider;
private final Map<String, ClientAuthenticationMethod> clientAuthenticationMethodMap =
new HashMap<>();
private final String authenticationMethod;
public OAuth2Configuration(
#Value("${spring.security.oauth2.client.registration.tenant1.client-authentication-method}")
final String authenticationMethod,
final ApplicationSecretProvider applicationSecretProvider) {
this.authenticationMethod = authenticationMethod;
this.applicationSecretProvider = applicationSecretProvider;
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.POST.getValue(), ClientAuthenticationMethod.POST);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.BASIC.getValue(), ClientAuthenticationMethod.BASIC);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.NONE.getValue(), ClientAuthenticationMethod.NONE);
}
#Bean
public InMemoryClientRegistrationRepository getClientRegistrationRepository(
OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
//We will have only one client registered for oAuth
if (CollectionUtils.isEmpty(registrations)) {
log.error(OAUTH2_REGISTRATION_MISSING);
throw new IllegalStateException(OAUTH2_REGISTRATION_MISSING);
}
ClientRegistration registration = registrations.get(0);
ClientRegistration.Builder builder = ClientRegistration.withClientRegistration(registration);
ClientAuthenticationMethod clientAuthenticationMethod =
getClientAuthenticationMethod(authenticationMethod);
ClientRegistration completeRegistration = builder
.clientSecret(applicationSecretProvider.getSecretForKey(OAUTH2_CLIENT_SECRET_KEY))
.clientAuthenticationMethod(clientAuthenticationMethod)
.build();
return new InMemoryClientRegistrationRepository(completeRegistration);
}
protected ClientAuthenticationMethod getClientAuthenticationMethod(String grantType) {
ClientAuthenticationMethod retValue = clientAuthenticationMethodMap.get(grantType);
if (retValue == null) {
return ClientAuthenticationMethod.NONE;
}
return retValue;
}
}
Then I extended DefaultOAuth2UserService in order to save user details in my application as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
private AuthorityRepository authRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Autowired
public void setAuthorityRepository(AuthorityRepository
authorityRepository) {
this.authorityRepository = authorityRepository;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
DefaultOAuth2User oAuth2User = (DefaultOAuth2User) super.loadUser(userRequest);
Collection<GrantedAuthority> authorities = new HashSet<>(oAuth2User.getAuthorities());
Map<String, Object> attributes = oAuth2User.getAttributes();
...
return new DefaultOAuth2User(authorities, oAuth2User.getAttributes(), userNameAttributeName);
}
}
Security configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customoAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customoAuth2UserService) {
this.customoAuth2UserService = customoAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()
//.and().oauth2ResourceServer().jwt()
.and()
//.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(customoAuth2UserService);
http.cors().disable();
}
}
Now, I would like to onboard multiple tenants using OAuth2 as well. Say I want to onboard another tenant tenant2. In order to achieve this, I think, I need to do the following changes in the existing code base as follows:
adding config entries in the properties file as above:
spring.security.oauth2.client.registration.tenant2.client-id=efgh
spring.security.oauth2.client.registration.tenant2.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.tenant2.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant2.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant2.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant2.user-name-attribute=name
I need to do changes in the security configuration class:
SecurityConfiguration and OAuth2 configuration class OAuth2Configuration as well. But I am not able to understand what should I add there in order to make my applications work seamlessly for multiple tenants.
In this context, I found this related post: Dynamically register OIDC client with Spring Security OAuth in a multi-tenant stup, but could not get any concrete idea regarding what changes should I do in the existing code base to make my application work in multi-tenancy set up.
Could anyone please help here?
I think there's a bit of confusion that it might help to clear up.
First, it seems that you are not actually building a resource server, as a resource server would require an access token for authentication. Using .oauth2Login() is for either OAuth 2.0 or OpenID Connect 1.0 login, which is a regular application in most respects except how you log in. You still have a browser session after login is successful, which you would not have in a resource server.
Second, configuring a static number of client registrations isn't really quite the same as building a multi-tenant application. Perhaps you're building up to that later, by demonstrating two clients. When configuring two clients using static configuration properties, nothing is really different from a single configuration, other than that there are two possible registrationIds.
Start by building a simple hello world application, such as the OAuth 2.0 Login Sample. If you add a second client registration to your properties, you'll notice that the auto-generated login page (/login) simply shows two links, one for each client. See docs for more on this.
The default URI for initiating the authorization_code flow is /oauth2/authorization/{registrationId}, which means navigating to /oauth2/authorization/abcd launches the first client's login flow. Navigating to /oauth2/authorization/efgh launches the second client's login flow. There's not really anything else needed to support multiple login clients other than understanding how to initiate login.
If you wish to support a fully multi-tenant login configuration, you would then provide a custom ClientRegistrationRepository, which you have done. The only difference is that you should no longer seek to configure clients through the Spring Boot properties, as that seems to be the point that is confusing in your example. If you want to use properties for some of the configuration, create your own configuration properties for your custom repository implementation. Typically at that point, all of this configuration would come from a database.
I would start with that progression (hello world, two statically configured clients, custom ClientRegistrationRepository) then proceed to add other custom components. It will help illustrate the differences at each point.

Spring security has SimpleGrantedAuthority but hasRole isn't working

I have the following code...
private static Collection<? extends GrantedAuthority>
readAuthorities(DecodedJWT jwt) {
...
return authorities;
}
It shows an Admin role. However, when I try to access a site by...
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.antMatchers(ADMIN).hasRole(Role.ADMIN.getRoleName())
....
}
// Role.ADMIN.getRoleName() == "Admin"
But when I go the the site requiring the admin permission I get a 403.
What am I missing?
As it turns out, spring security automatically prepends "ROLE_" onto role security requirements for things like .hasRole() and #Secured. So, you have to prepend "ROLE_" onto the roles when you assign them to users. If this is confusing, let me know and I'll go into more depth with examples from my project, I've just been working on this project for the last 12 hours and don't have time right now.

Spring Security OAuth2 - optional login with server check

I'm working on a Web Project with different Spring Boot WebMVC Clients. Some of this Clients needs a authorization and I solved it with a Spring Security OAuth2 Server. The authentication works fine and I had no problems. Some Clients didn't need an login and they are public for all.
Technical facts: All clients use a mix between Angular, jQuery and simple JSP's. All apps use Spring Security and the public app configuration is like this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.anyRequest().authenticated();
}
Now my question: I plan to build a Login (or Logout) Button in the Header of all apps. In the apps with a required authentication is that no problem. I can check the principal is null or not. But how can I solve this in public apps. The principal is ever null and the client didn't check the authentication status with the server. I had some ideas to fix it but nothing is working. The best way would be a automatic check in Spring Boot. But how can I configure this? Maybe I can check it with JavaScript, but my shots also didn't work.
Maybe it would help - two of my apps:
https://www.planyourtrip.travel (public application)
https://profile.planyourtrip.travel (memberonly application)
UPDATE: Maybe a better example
If I configure a public app like this
#Configuration
#EnableOAuth2Sso
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and the MVC Controller like this
#RequestMapping("/{([a-z]{2})}")
public ModelAndView start(final Principal principal) {
return new ModelAndView("start");
}
then is the Principal ever null. I think that is my Problem. I need a check with the OAuth Server and if i logged in is the principal set and if I'm not logged in it should be null.
If I had understood your question correctly, than you need that some URL pattern can be accessed without authentication. Than in that case you can use the following method to prevent authentication for certain URL patterns -
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/urlPattern");
}
permitAll() method defines that all the authenticated users can access mentioned URL pattern. So if you want some users to access some resources (URL) without authentication, than you have to use above method.

Spring OAuth2 - There is no client authentication. Try adding an appropriate authentication filter

We have an application which is using spring-security-oauth2:1.0. I was trying to change it to a newer version, spring-security-oauth2:2.0.7.RELEASE. Some classes were removed, some package structure is changed, I managed to sort out all those things and I was able to start the server without any issue. But I am facing a strange issue here.
With OAuth2 - 1.0 version, when the user logs in we used to do a GET request on /oauth/token, For example :
http://localhost:8080/echo/oauth/token?grant_type=password&client_id=ws&client_secret=secret&scope=read,write&username=john#abc.com&password=password123
and It used to work just fine.
When I try the same thing, First of all I am not able to make a GET request because of the logic in TokenEndPoint.java
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
#RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
I have tried to make a POST request same as above URL, but I get InsufficientAuthenticationException with the error message
There is no client authentication. Try adding an appropriate authentication filter
This is because of the following POST request controller in TokenEndpoint.java. When I debug, I see that principal is null.
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
//principal is null here
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
.............
}
I have an authentication filter and it worked well when I used version 1.0. This is the relevant prats of my config:
<authentication-manager xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<bean id="userDetailsService" class="com.hcl.nc.service.UserDetailsService">
<constructor-arg><ref bean="sessionFactory" /></constructor-arg>
</bean>
I always thought that the request will be authenticated by authentication-provider and goes to token-endpoint but that does not seem to be the correct flow. After debugging the application with version 2.0.7, now I really doubt my understanding about the flow.
Could somebody please explain why it worked in previous version and why it's not working now?
Do I have do to something different to get a OAuth token??
NOTE: I have already checked these questions : here, here, here. But I was not able to find the correct solution.
I don't know the previous version, but I know a bit about 2.0.7.
I suspect your problem is that your TokenEndpoint security tries to authenticate your clients against your user service.
The TokenEndpoint is protected by a BasicAuthenticationFilter. By default this filter would use an AuthenticationManager instance, which itself holds an AuthenticationProvider, which itself depends on an instance of UserDetailsService.
The trick is that this particular instance of UserDetailsService must be client based, not user based : that's why there is a ClientDetailsUserDetailsService, which adapts ClientDetailsService to UserDetailsService.
Normally all this stuff is already done by default when you use the framework's configuration classes AuthorizationServerConfigurerAdapter, #EnableAuthorizationServer, etc..
I had the same problem and my application.yml had this line:
servlet:
path: /auth
so the token address was: /auth/oauth/token
I remove the path from application.yml so the token path became:
/oauth/token
And everything works fine.
I hope this help
One of the problems of the following error, can be that authentication was not performed. I have encountered this problem with older implementation of Spring.
verify that:
TokenEndpoint -> postAccessToken method. Check if Principal is not null. If it is null it means that Basic Authroziation was not performed.
One of the solution to add filter was to use:
#Configuration
public class FilterChainInitializer extends AbstractSecurityWebApplicationInitializer {
}
More information about AbstractSecurityWebApplicationInitializer can be found in Spring docs
The problem can be because of opening all requests. You should remove it.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/**");
}
in my case, i found this config:
security.allowFormAuthenticationForClients(); // here
then post this
http://localhost:8081/sso/oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=Yk4Sum&redirect_uri=http://localhost:8082/sso-demo/passport/login
its works for me, try it
#Configuration
#EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(Oauth2Config.class);
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); // here
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { // #formatter:off
clients.inMemory()
.withClient("unity-client")
.secret("unity")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
;
} // #formatter:on
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
I am following this tutorial - Practical Guide to Building an API Back End with Spring Boot'. See https://www.infoq.com/minibooks/spring-boot-building-api-backend , But with the latest SpringBoot Version(2.7)
and I run into this problem:
org.springframework.security.authentication.InsufficientAuthenticationException: There is no client authentication. Try adding an appropriate authentication filter. at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:91) ~[spring-security-oauth2-2.3.5.RELEASE.jar:na]
My solution/fix was to annotate WebSecurityGlobalConfig with #EnableWebSecurity because in the original course this annotation was missing.
So adding this annotaiton has fixed the error for me.

Resources