I am trying to create a spring resource server secured with oauth2.
I am using auth0 for my auth2 service, and I have an api and client configured with scopes.
I have a resource server that mostly works. It is secured, and I can use #EnableGlobalMethodSecurity and #PreAuthorize("#oauth2.hasScope('profile:read')") to limit access to tokens with that scope.
However, when I try to get the Principal or the OAuth2Authentication they are both null. I've configured the resource server to use the JWK key-set-uri.
I suspect that this has to do with the DefaultUserAuthenticationConverter trying to read the the 'user_name' claim form the JWT, but it needs to be reading it from the 'sub' claim, and I don't know how to change this behaviour.
First create a UserAuthenticationConverter:
public class OidcUserAuthenticationConverter implements UserAuthenticationConverter {
final String SUB = "sub";
#Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) {
throw new UnsupportedOperationException();
}
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(SUB)) {
Object principal = map.get(SUB);
Collection<? extends GrantedAuthority> authorities = null;
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
}
Then configure spring to use it like so:
#Configuration
public class OidcJwkTokenStoreConfiguration {
private final ResourceServerProperties resource;
public OidcJwkTokenStoreConfiguration(ResourceServerProperties resource) {
this.resource = resource;
}
#Bean
public TokenStore jwkTokenStore() {
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(new OidcUserAuthenticationConverter());
return new JwkTokenStore(this.resource.getJwk().getKeySetUri(), tokenConverter);
}
}
Related
I'm using ASP.NET Core and hosting what is basically the default template with Windows Authentication enabled. I'm hosting this on a dedicated IIS server, and have verified the app is receiving correct information from AD and it correctly authenticates my session.
I feel like I'm trying to do something very simple. If the user is in the security group (from AD) "Admin" they are able to access a specific function. If they aren't in that group they do not get access.
I slapped on the [Authorize] attribute to the service
(in ConfigureServices)
services.AddAuthentication(IISDefaults.AuthenticationScheme);
(in Configure)
app.UseAuthorization();
(in service)
[Authorize]
public class SiteService
{
private readonly string _route;
private readonly HttpClient _httpClient;
public SiteService(HttpClient httpClient)
{
_httpClient = httpClient;
_route = httpClient.BaseAddress.AbsoluteUri;
}
public async Task<IEnumerable<Site>> GetSites()
{
}
}
I can see in the logs that accessing the service gives me Domain/User. I then looked up the MS Docs here: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-3.1
And slapped on [Authorize(Roles = "Admin"). That worked. I then switched "Admin" with "sldkfjslksdlfkj". Nothing changed...I can still access the service.
Why is the Roles="x" check not working? How can I enable a relatively simple check to AD for a Security Group?
You could write a custom Policy Authorization handlers to check all of the users' ADGroups and check if they contain the desired group name.
Refer to the following:
1.Create CheckADGroupRequirement(accept a parameter)
public class CheckADGroupRequirement : IAuthorizationRequirement
{
public string GroupName { get; private set; }
public CheckADGroupRequirement(string groupName)
{
GroupName = groupName;
}
}
2.Create CheckADGroupHandler
public class CheckADGroupHandler : AuthorizationHandler<CheckADGroupRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
CheckADGroupRequirement requirement)
{
//var isAuthorized = context.User.IsInRole(requirement.GroupName);
var groups = new List<string>();//save all your groups' name
var wi = (WindowsIdentity)context.User.Identity;
if (wi.Groups != null)
{
foreach (var group in wi.Groups)
{
try
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (Exception e)
{
// ignored
}
}
if(groups.Contains(requirement.GroupName))//do the check
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
3.Register Handler in ConfigureServices
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.Requirements.Add(new CheckADGroupRequirement("DOMAIN\\Domain Admin")));//set your desired group name
//other policies
});
services.AddSingleton<IAuthorizationHandler, CheckADGroupHandler>();
4.Use on controller/service
[Authorize(Policy = "AdminOnly")]
public class SiteService
I succesfully added user_id additionnal information on the generated tokens on the authorization server side by implementing a TokenEnhancer. Here is a token generated:
{"access_token":"ccae1713-00d4-49c2-adbf-e699c525d53e","token_type":"bearer","expires_in":31512,"scope":"end-user","user_id":2}
Now, on the Resource server side, which is a completely separate spring project communicating through a RemoteTokenServices, i would like to use theses informations with method expression-based access control. For example i would like to use the added user_id data (it is Spring Data JPA repository for use with Spring Data Rest):
#PreAuthorize("#oauth2.hasScope('admin') or #id == authentication.principal.user_id")
#Override
UserAccount findOne (#P("id") Integer id);
The #oauth2.hasScope('admin') works as expected but the #id == authentication.principal.user_id" part obviously not.
how can i access to the additional data added to the token on expression-based access control ?
So i've found myself. The key interface is UserAuthenticationConverter.
Using the default provided DefaultUserAuthenticationConverter class, we can set a UserDetailsService which is used to set authentication.principal with the UserDetail object returned by the UserDetailsService. Without that, authentication.principal is only set with the token username as a String.
Here is an extract of my ResourceServerConfigAdapter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration
extends ResourceServerConfigurerAdapter {
#Bean
UserDetailsService userDetailsService () {
return new UserDetailsServiceImpl();
}
#Bean
public UserAuthenticationConverter userAuthenticationConverter () {
DefaultUserAuthenticationConverter duac
= new DefaultUserAuthenticationConverter();
duac.setUserDetailsService(userDetailsService());
return duac;
}
#Bean
public AccessTokenConverter accessTokenConverter() {
DefaultAccessTokenConverter datc
= new DefaultAccessTokenConverter();
datc.setUserTokenConverter(userAuthenticationConverter());
return datc;
}
#Bean
RemoteTokenServices getRemoteTokenServices () {
RemoteTokenServices rts = new RemoteTokenServices();
rts.setCheckTokenEndpointUrl(
"http://localhost:15574/oauth/check_token");
rts.setAccessTokenConverter(accessTokenConverter());
rts.setClientId("client");
rts.setClientSecret("pass");
return rts;
}
...
}
Another method is to override the DefaultUserAuthenticationManager and provide a custom public Authentication extractAuthentication(Map<String, ?> map).
Once this is done, we can use the user data on expression-based access control like that:
#PreAuthorize("#oauth2.hasScope('admin') or #id == authentication.principal.userAccount.id")
#Override
UserAccount findOne (#P("id") Integer id);
Note that userAccount is my original DOMAIN user object. It could be everything the UserDetailsService returns.
EDIT:
To answer to Valentin Despa, here is my UserDetailsService implementation:
#Component
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserAccountRepository userAccountRepository;
public UserDetails loadUserByUsername (String username)
throws UsernameNotFoundException {
// Fetch user from repository
UserAccount ua = this.userAccountRepository
.findByEmail(username);
// If nothing throws Exception
if (ua == null) {
throw new UsernameNotFoundException(
"No user found having this username");
}
// Convert it to a UserDetails object
return new UserDetailsImpl(ua);
}
}
I have a RequestInterceptor where I automatically copy an AccessToken from OAuth2ClientContext into the RequestTemplate's header so that the internal services are seamlessly calling one another with the same AccessToken that came from the mobile device that started the scenario.
And that's how we manage services methods authorization.
This is the interceptor code:
public class FeignOAuthInterceptor implements RequestInterceptor {
private OAuth2ClientContext oauth2ClientContext;
public FeignOAuthInterceptor (OAuth2ClientContext oauth2ClientContext) {
this.oauth2ClientContext = oauth2ClientContext;
}
#Override
public void apply(RequestTemplate template) {
if (!template.headers().containsKey(PropertyBagFilter.AUTHORIZATION_HEADER) && oauth2ClientContext.getAccessTokenRequest().getExistingToken() != null) {
template.header(PropertyBagFilter.AUTHORIZATION_HEADER, String.format("%s %s", PropertyBagFilter.BEARER_TOKEN_TYPE,
oauth2ClientContext.getAccessTokenRequest().getExistingToken().toString()));
}
}
}
and this is the #Beans configuration:
#Bean
public OAuth2ClientContext oAuth2ClientContext (){
return new DefaultOAuth2ClientContext();
}
#Bean
public RequestInterceptor feignOAuthInterceptor(OAuth2ClientContext oauth2ClientContext) {
return new FeignOAuthInterceptor(oauth2ClientContext);
}
#Bean
public OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails(){
return new ResourceOwnerPasswordResourceDetails();
}
The problem is that there are different FeignClients and part of them are for 3rd party services, such as a services which we use for SMS texts and I don't want to send the AccessToken there.
How can I determine inside the RequestInterceptor what FeignClient it came from?
Using Dropwizard Authentication 0.9.0-SNAPSHOT
I want to check the credentials against database user (UserDAO).
I get the following exception
! org.hibernate.HibernateException: No session currently bound to
execution context
How to bind the session to the Authenticator?
Or are there better ways to check against the database user?
The Authenticator Class
package com.example.helloworld.auth;
import com.example.helloworld.core.User;
import com.example.helloworld.db.UserDAO;
import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDAO;
public ExampleAuthenticator(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
Optional<User> user;
user = (Optional<User>) this.userDAO.findByEmail(credentials.getUsername());
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
The Application Class
#Override
public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
final UserDAO userDAO = new UserDAO(hibernate.getSessionFactory());
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator(userDAO))
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(new UserResource(userDAO));
To get auth to work with 0.9+ you need the following. You can refer to this particular changeset as an example.
Include the dependency.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
</dependency>
Register auth related stuff.
private void registerAuthRelated(Environment environment) {
UnauthorizedHandler unauthorizedHandler = new UnAuthorizedResourceHandler();
AuthFilter basicAuthFilter = new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new BasicAuthenticator())
.setAuthorizer(new UserAuthorizer())
.setRealm("shire")
.setUnauthorizedHandler(unauthorizedHandler)
.setPrefix("Basic")
.buildAuthFilter();
environment.jersey().register(new AuthDynamicFeature(basicAuthFilter));
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(unauthorizedHandler);
}
A basic authenticator
public class BasicAuthenticator<C, P> implements Authenticator<BasicCredentials, User> {
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
//do no authentication yet. Let all users through
return Optional.fromNullable(new User(credentials.getUsername(), credentials.getPassword()));
}
}
UnAuthorizedHandler
public class UnAuthorizedResourceHandler implements UnauthorizedHandler {
#Context
private HttpServletRequest request;
#Override
public Response buildResponse(String prefix, String realm) {
Response.Status unauthorized = Response.Status.UNAUTHORIZED;
return Response.status(unauthorized).type(MediaType.APPLICATION_JSON_TYPE).entity("Can't touch this...").build();
}
#Context
public void setRequest(HttpServletRequest request) {
this.request = request;
}
}
Authorizer
public class UserAuthorizer<P> implements Authorizer<User>{
/**
* Decides if access is granted for the given principal in the given role.
*
* #param principal a {#link Principal} object, representing a user
* #param role a user role
* #return {#code true}, if the access is granted, {#code false otherwise}
*/
#Override
public boolean authorize(User principal, String role) {
return true;
}
}
Finally use it in your resource
#GET
public Response hello(#Auth User user){
return Response.ok().entity("You got permission!").build();
}
You're going to need code in your Application class that looks like this
environment.jersey().register(AuthFactory.binder(new BasicAuthFactory<>(
new ExampleAuthenticator(userDAO), "AUTHENTICATION", User.class)));
Then you can use the #Auth tag on a User parameter for a method and any incoming authentication credentials will hit the authenticate method, allowing you to return the correct User object or absent if the credentials are not in your database.
EDIT: Works for Dropwizard v0.8.4
On Latest versions starting from 0.9 onward, you can use "#Context" annotation in resource class methods as shown below:
#RolesAllowed("EMPLOYEE")
#Path("/emp")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getEmployeeResponse(#Context SecurityContext context) {
SimplePrincipal sp = (SimplePrincipal) context.getUserPrincipal();
return Response.ok("{\"Hello\": \"Mr. " + sp.getUsername() + "\"( Valuable emp )}").build();
}
I want to access the logged-in user from a session-scoped spring bean, is that possible?
I'm not using spring security, but openam instead to provide security to my web application, so I can't use this (as I've seen in many examples on the internet):
(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Is it possible to inject into my session-scoped bean the same name that you get from:
HttpServletRequest.getUserPrincipal().getName()
You can try creating an Interceptor and setting the logged in user to a property of your session bean, which can be injected into your interceptor.
Like this:
public class SessionDataInitializerInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOG = Logger.getLogger(SessionDataInitializerInterceptor.class);
#Autowired
SessionData sessionData;
public SessionDataInitializerInterceptor() {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Principal principal = request.getUserPrincipal();
if (principal != null) {
if (sessionData.getUser() == null) {
sessionData.setUser(principal.getName());
}
} else {
LOG.error(String.format("No user principal in request for url[%s]", request.getRequestURL().toString()));
}
return true;
}
}
Don't forget to map your interceptor to the appropriate URLs.
Also this works:
#Component
#SessionScope
public class SessionData {
#Autowired
private HttpServletRequest request;
#PostConstruct
public void init() {
Principal principal = request.getUserPrincipal();
}
}