Is it ok to add access_token authorities to the OAuth2LoginAuthenticationToken? - authorization

I have a simple spring boot application with two services - ui and resource.
I trying to configure oauth2+oidc authentication using uaa server.
When I login in the ui service, spring security creates authentication result (in OidcAuthorizationCodeAuthenticationProvider) using id_token and it doesn't contain any scopes except openid. When the authentication result is created it contains only one authority - ROLE_USER so a can't use authorization on the client side.
Is is ok to override OidcUserService and add to the user's authorities scopes from the access_token to check access on the client side?
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = super.loadUser(userRequest);
Collection<? extends GrantedAuthority> authorities = buildAuthorities(
user,
userRequest.getAccessToken().getScopes()
);
return new DefaultOidcUser(
authorities,
userRequest.getIdToken(),
user.getUserInfo()
);
}
Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/protected/**").hasAuthority("SCOPE_protected")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint().oidcUserService(oidcUserService())
.and()
...
It works but I'm not sure it's a good idea.

It is the approach as outlined in the Spring Security documentation, so the approach is fine.
The only thing is that when I have implemented it, I didn't add all the scopes to the authorities set - I pulled out the specific claim that had the role information - a custom groups claim that I configured in the identity provider's authorization server.
I include some example code for how to do this with Spring Webflux as most examples show how to do it with Spring MVC as per your code.
note: I'm very inexperienced with using reactor!
public class CustomClaimsOidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcReactiveOAuth2UserService service = new OidcReactiveOAuth2UserService();
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
log.debug("inside CustomClaimsOidcReactiveOAuth2UserService..");
Mono<OidcUser> mOidcUser = service.loadUser(userRequest);
return mOidcUser
.log()
.cast(DefaultOidcUser.class)
.map(DefaultOidcUser::getClaims)
.flatMapIterable(Map::entrySet)
.filter(entry -> entry.getKey().equals("groups"))
.flatMapIterable(roleEntry -> (JSONArray) roleEntry.getValue())
.map(roleString -> {
log.debug("roleString={}", roleString);
return new OidcUserAuthority((String) roleString, userRequest.getIdToken(), null);
})
.collect(Collectors.toSet())
.map(authorities -> {
log.debug("authorities={}", authorities);
return new DefaultOidcUser(authorities, userRequest.getIdToken());
});
}
}
...
#Bean
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService() {
return new CustomClaimsOidcReactiveOAuth2UserService();
}

Related

I am receiving status 401 Unauthorized when I attempt to call my secured API by keycloak

I have an web application providing an REST API endpoints, secured with spring security and SSO Keycloak.
My web application works fine with protection on and I can access REST API endpoints using web browser, after authentication on keycloak and redirect back to endpoint. I am getting expected JSON response, which mean user and role should be configured correctly.
But when i want to call a provided REST API endpoint from another web application or using Postman, i have every time an error 401 error: unauthorized.
In the header of the request I am putting:
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgO...." token.
I am able to fetch this token from keycloak in consumer web application and also when I am making a request using postman.
My Keycloak configuration in my spring security config:
#KeycloakConfiguration
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/api/v1/**").hasRole("USER")
.antMatchers("/admin/**", "/app/**").hasRole("ADMIN")
.anyRequest().permitAll();
}
}
My keycloak.json file:
{
"realm": "realm-name",
"auth-server-url": "https://auth.server.com/auth/",
"ssl-required": "external",
"resource": "resource-name",
"verify-token-audience": true,
"credentials": {
"secret": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"use-resource-role-mappings": true,
"confidential-port": 0
}
My method in the controller of the api:
#RestController
#RequestMapping("/api/v1")
public class WakeMeUpController {
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
public String testMeUp() {
return "I am tested!";
}
}
This is a part of my client configuration in keycloak:
keycloak Client screen
Rest keycloak config is how it is coming by default.
Thx for your help!
Thank you. The problem was into audience configuration in keycloak.
For a client, you need to configure new Mapping under Client -> <client_name> -> Mappers and add another Mapping with mapper type 'Audience' and appropriate 'Included Client audience' (i choose client id i have used to make request)
Also another problem that i have faced after i solve this one, was that user was authenticated but was forbidden the access to endpoint.
Explicitly adding role into client configuration itself and assigning this role to the user solve it and it is working.
From huge help was setting spring logging to DEBUG
logging.level.org.springframework.security=DEBUG
And also XXX-Authentication header in server response that gives better description what the issue is.

Is there a way to get a non-null Principal/Authentication using Spring Security with anonymous access?

I'm using Spring Security, and want to be able to run with a "local" profile with security off. I can do this by using a profile-controlled WebSecurityConfigurerAdapter which allows anonymous access, but the problem is that any Principal or Authentication arguments to controller methods are null.
The way I've solved this before is by passing the arguments through an adapter, which has a non-local pass-through implementation and a "local"-profile implementation that substitutes a configured value:
#GetMapping()
public ResponseEntity<List<Thing>> getThings(Principal principal) {
Principal myPrincipal = securityAdapter.principal(principal);
...
}
But this feels really clunky. Is there some way to configure Spring Security to make the substitution so that I can keep the code clean?
Unfortunately it is a SpringSecurity feature: the AnonymousAuthenticationToken is not injected in controller methods. I could find some references for that in this issue.
A possible workaround (from above issue) is to inject a custom trust resolver that pretends that the AnonymousAuthenticationToken is not anonymous...
#Bean
public AuthenticationTrustResolver trustResolver() {
return new AuthenticationTrustResolver() {
#Override
public boolean isRememberMe(final Authentication authentication) {
return false;
}
#Override
public boolean isAnonymous(final Authentication authentication) {
return false;
}
};
}
Rather ugly, but as it is intended to run in a profile where anonymous authentication is expected to be valid, it could be enough.

Spring Security With Custom Login Flow

I am trying to add spring with spring security in an existing Struts based application. It is a Partial migration. All the new development should happen in Spring.
So what I want is,
User access a Secured URL
If the user is not authenticated Spring Should redirect to a specific URL (This will be a Struts URL)
After Old Struts Module does its work of authenticating the user, It saves an Object in HTTP Session which depicts the authenticated User.
Spring Should get this Object from Session and Authenticated the User.
Redirect the User to the requested Secured URL
I have tried to used formlogin().loginpage(<Struts_URL>). Also in the Struts code after authentication, I have updated the Spring's SecurityContext.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity pHttp) throws Exception
{
pHttp.antMatcher("/**").authorizeRequests()
.anyRequest().authenticated()
.and().formLogin().loginPage("/<Struts_Login_Entry>.do");
}
...
#Override
public void configure(WebSecurity pWeb) throws Exception
{
// All the Strtus URL has .do suffix so excluding it
pWeb.ignoring().antMatchers("/*.do", "/portal/**");
}
}
After Authentication in Struts
...
MyUserDetails lUserDetails = new MyUserDetails(pUserName, pRole);
Authentication auth = new UsernamePasswordAuthenticationToken(lUserDetails, null, lUserDetails.getAuthorities());
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
...
The User is getting redirected to Struts page when he accesses any Spring URL But this happens even if the user is authenticated.
I am not so familiar with Spring Security, any suggestion or help?

Is is possible to disable authentication providers for specific routes?

We're evaluating service stack v.4.5.6.0 for a Web API and we want clients to be able to authenticate using basic auth or credentials but we do not want them to be able to provide a basic auth header in place of a JWT token or session cookie when using our services. While I realize this is somewhat arbitrary, is there a way to exclude routes from specific providers or force the use of a token/cookie to authenticate once they've logged in?
Auth config from AppHost:
private void ConfigureAuth(Container container)
{
var appSettings = new AppSettings();
this.Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[]
{
new CredentialsAuthProvider(),
new BasicAuthProvider(),
new JwtAuthProvider(appSettings)
}) { IncludeAssignRoleServices = false, MaxLoginAttempts = 10} );
var userRepository = new CustomUserAuthRepository(container.TryResolve<IDbConnectionFactory>());
container.Register<IAuthRepository>(userRepository);
}
ServiceStack lets you decide which AuthProviders you want your Services to be authenticated with, but it doesn't let you individually configure which adhoc AuthProviders applies to individual Services. Feel free to add this a feature request.
However if you want to ensure that a Service is only accessed via JWT you can add a check in your Services for FromToken which indicates the Session was populated by a JWT Token, e.g:
[Authenticate]
public class MyServices : Service
{
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (!session.FromToken)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
}
From v4.5.7 that's now available on MyGet you can also use the new session.AuthProvider property which indicates what AuthProvider was used to Authenticate the user, e.g:
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (session.AuthProvider != JwtAuthProvider.Name)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
Refer to the docs for different AuthProvider names for each AuthProvider.

How secure Rest-API by token

i use spring data rest 2.1 and would like secure my rest api, what is the best way to secure the api by token. I would have an process like the following steps. Is it also possible to change the base url from / to /api , but my controllers like 'home' would also display the jsp side from /home but not from /api/..
User go to login site
User login and get an token from server
User go to dashboard site and js do ajax call and set HTTP-Header Field 'X_AUTH_TOKEN' with token
You could configure base URI for you API using RepositoryRestConfiguration. Something like this:
#Configuration
public static class RepositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
try {
config.setBaseUri(new URI("/api"));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
You could add security using Spring-Security or if you like a simple custom solution use Spring Interceptor