Spring Security SAML Extension and #PreAuthorize - spring-mvc

My requirements are to use SAML based SSO. Retrieve the user groups from SAML assertions and secure the rest api endpoints. I am using Spring security SAML extension and Spring MVC. The steps I have taken are.
Configure the application for SP using Spring SAML extension. [Done]
Retrieve assertions and assign roles [Done]
Create rest endpoint. [Done]
Secure rest endpoint and services based on roles. [Not working]
I have implemented SAMLUserDetailsService which returns a UserDetails object with authorities. 'loadUserBySAML' below.
#Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
final String userId = credential.getNameID().getValue();
final String emailAddress = credential.getAttributeAsString("EmailAddress");
final String firstName = credential.getAttributeAsString("FirstName");
final String lastName = credential.getAttributeAsString("LastName");
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_STUDENT"));
return new User(userId, emailAddress, firstName, lastName, authorities);
}
I have added <!-- Enable security annotations on methods -->
<security:global-method-security pre-post-annotations="enabled" />to the securityContext.xml.
On the RestController and on the services I am using #PreAuthorize but this annotation seems to have no effect at all.
#PreAuthorize("hasRole('ROLE_PROGRAMLEAD')")
#RequestMapping(method = RequestMethod.GET)
public String hello() {
return "Hello.";}
Could someone please help me understand why the PreAuthorize is not firing? Am I missing some configuration?

I was facing the same issue. I wanted to perform API authorisation with SAML authentication. For it to work, you need to use the hasAuthority param with the annotation instead of the hasRole param.
The following worked for me:
#PreAuthorize(value="hasAuthority('Admin')")

The <security:global-method-security pre-post-annotations="enabled" /> needs to go in the servlet xml of the rest controller and not the security context xml.
Reference:
http://docs.spring.io/spring-security/site/faq/faq.html#faq-method-security-in-web-context

Related

Allow user only access his/her own resource with id in Authorize[] middleare .Net Core Api

I am using role based authentication in .Net Core 3.1 Api. I am using Jwt tokens and user claims. Role based authentication works fine. But in some controllers I want to make sure that user gets his/her own data. Because if an employee sends other employee id in a request he/she can get that resource data, I don't want that.
I have email, id and roles in token with some other data.
What I want is that something like [Authorize(Roles="Employee", Id={userId})]
[HttpGet("getUserInventory")]
//[Authorize(Roles="Employee", Claims.Id={userId})]
public IActionResult getUserInventory([FromQuery] int userId)
{
var inventories = _userInventoryExportService.GetGlobalInventory(userId);
if(inventories.Success)
{
return Ok(inventories.Data);
}
return BadRequest(inventories.Message);
}
Have a look at this tutorial we've created at Curity: Securing a .NET Core API. You will see there how to configure authorization based on claims found in a JWT access token.
had the same use case, to authorize user access to its own mailbox only.
controller:
[HttpPost("{address}/inbox/messages/list")]
[Authorize(Policy = "userAddress")]
public async Task<ActionResult<Response>> ListMessages([FromRoute] string address)
{
// return user mailbox data.
}
here i define the userAddress, and also the way i pull the address string from the url. it is not possible to pass this value from the controller, i had to pick it from a global request class:
//Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("userAddress", policy =>
{
policy.RequireAssertion(context =>
{
var userAddress = context.User.FindFirst(JWTClaim.Email).Value;
// /api/v1/mailbox/email#example.com/inbox/messages/list
var address = new HttpContextAccessor().HttpContext.Request.RouteValues["address"].ToString();
return address == userAddress;
});
});
});
it is worth to note that the context contains the actual request values, but is not publicly accessible, only via debugger:
context.Resource.HttpContext.Request.RouteValues["address"].ToString();

Use another auth method for external api calls

I have a Web API application with MVC. When a user is using the website, the authentication and authorization is currently automatically handled by the global forms authentication I use, configured in the Web.config like so:
<authentication mode="Forms">
<forms loginUrl="~/Login" slidingExpiration="true" timeout="1800" defaultUrl="/"></forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
This makes sure only logged in users can access the site and call the API.
But I also have an external Windows client for which I would like to use another authentication method. In a test without the forms auth, I set up a custom AuthorizeAttribute that I can use in my controllers like this:
[ApiAuth]
public IEnumerable<string> Get() {
// Return the resource
}
The AuthorizeAttribute looks something like this:
public class ApiAuthAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext context) {
// Authenticate the request with a HMAC-based approach
}
}
This works fine in isolation but I cannot figure out how to allow both auth methods. I would like to the ApiAuth as a fallback if the form auth doesn't work (or the reverse, whatever works), but if I apply the [ApiAuth] attribute, only that will be used and normal users cannot access the api.
So, how can I use multiple auth methods, either by using one of them as a fallback if the other one fails, or configuring the server so the Windows client can call the API some other way then the MVC app, while still keeping the same API calls available to both type of clients?
Thank you.
Edit: One approach that I could probably take, is to let the Windows client authenticate using the forms auth (something like this), but it seems very much like a hack and I would much rather use some other approach.
FormAuthentication can be achieve multiple way. In old day, we use FormAuthentication Ticket.
Now, you can use claim-based authentication with Owin Middleware which basically is a strip down version of ASP.Net Identity.
After you authenticate a user inside ApiAuthAttribute, you create Principal object.
Web.config
You should not use <authorization> tag in ASP.Net MVC. Instead, you want to use Filter.
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
ApiAuthAttribute
public class ApiAuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
// Authenticate the request with a HMAC-based approach
// Create FormAuthentication after custom authentication is successful
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
User user = new User {Id = "1234", UserName = "johndoe",
FirstName = "John", LastName = "Doe"};
// This should be injected using IoC container.
var service = new OwinAuthenticationService(
new HttpContextWrapper(HttpContext.Current));
service.SignIn(user);
}
}
}
Authentication
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public interface IAuthenticationService
{
void SignIn(User user);
void SignOut();
}
public class OwinAuthenticationService : IAuthenticationService
{
private readonly HttpContextBase _context;
private const string AuthenticationType = "ApplicationCookie";
public OwinAuthenticationService(HttpContextBase context)
{
_context = context;
}
public void SignIn(User user)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
};
/*foreach (Role role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name));
}*/
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignIn(identity);
}
public void SignOut()
{
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignOut(AuthenticationType);
}
}
Startup.cs
[assembly: OwinStartup(typeof(YOUR_APPLICATION.Startup))]
namespace YOUR_APPLICATION
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login")
});
}
}
}
I implemented something similar a while back. You may want to look at third party auth providers (as they have been tested). If you create your own mechanism make sure that whatever data you store to identify an authenticated user session will be removed based on some expiration value.
When I refer to a token below, please note that I am refering to a hash using a combination of :
Some user data
Some dynamic data such as tick count
Some data that represents what resource is being requested
Maybe the parameters
For example. You could hash the username/hh:mm:ss:ms/fully qualified path/enpoint/enpoint parameters into your user's token. Then you have to decide if the token will be valid on a sliding expiration, 30 minutes, or is it only valid per request.
I would add an anonymous endpoint for your test application to authenticate against. This endpoint should accept user credentials and return a token that matches an entry in Ticket table that represents the user with an expiration. Essentially, since you are not attaching a ticket to each request you will have to manage this yourself in some fashion as I have suggested using the http authorization header.
public ActionResult GetAuthententicationToken(Credentials credentials)
{
//Authenticate the user
//Insert a record into the Ticket database table and return hash key as token.
//Return the token to the client.
}
Now the client ,your testing app, has been authenticated against an existing set of credentials and has a token representing that handshake.
Your test app now only has to sign the authorization http header with the value returned from get GetAuthententicationToken().
Now you can implement your AuthorizeAttribute in which case you want to validate the authorization header token with what was previously stored with a successful call to your anonymous GetAuthententicationToken method.
public class ApiAuthAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext context) {
//Get authorization token from header
//if caching then get associated Ticket from cache else lookup in database
//if not valid throw security exception
//Apply principal to current user based on lookup above
}
}
So how to handle FormsAuthentication with the above scheme in mind?
Since Forms Authentication is handled earlier in the request processing than the MVC Authorize you have a perfect opportunity to add your custom authorization header to the incoming request when the user is authenticated via your forms method.
In the same place that you authenticate your forms authentication add something similar to below.
public FormsAthentication.CreateAuthenticationTicket()
{
//Authenticate user
//Insert a record into the Ticket database table and return hash key as token.
//Add that token to ticket's data
}
Next, you need to make sure the custom authorization header is applied per request. The best place to do this would be the Application_AuthenticateRequest in the Global.asax file.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//if FormsAuthentication.IsAuthenticated
//Get the token saved in the ticket data
//Save the token value in the http authorization header
}
NOTE : The Ticket database table mention above should save a valid authentication request with a datetime stamp for expiration date. You must ensure that you have a process that runs in the background to enforce the timeout by removing expired session records.

Spring OAuth2 Authentication object is null with CustomAuthenticationProvider

I am using Spring OAuth2 to protect my REST API's and implemented client_credentials, password grant types. They work perfectly fine except ResourceOwnerPassword flow with user credentials in the POST body.
Token endpoint call with the below URL is OK (If we send the user credentials as URL parameters)
HTTP POST: /oauth/token?grant_type=password&username=123&password=456
But with the below setup, my Authentication.getCredentials and Authentication.getPrincipal Objects are always empty.. I am using a custom authentication provider since I need to validate user against an external LDAP system.
Does this mean user credentials(Usernamd and password) can only be sent in the URL parameters to the token endpoint(/oauth/token)??
My configurations are:
Authorization server configuration:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter));
endpoints
.tokenStore(tokenStore).tokenEnhancer(tokenEnhancerChain)
.userApprovalHandler(userApprovalHandler)
.authenticationManager(userAuthenticationManager);
}
#Bean
AuthenticationManager userAuthenticationManager() {
List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>();
authenticationProviders.add(new CustomAuthenticationProvider()));
return new ProviderManager(authenticationProviders);
}
Custom Authentication provider:
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
System.out.println("authentication object........"+authentication);
String userName;
String password;
System.out.println("authentication object credentials........"+authentication.getCredentials());
userName = authentication.getName();
password = authentication.getCredentials().toString();
}
Authentication object printed in the console:
org.springframework.security.authentication.Usernam
ePasswordAuthenticationToken#7a2162f9: Principal: null; Credentials: [PROTECTED]
; Authenticated: false; Details: {grant_type=password}; Not granted any authorities
However, I did find a post where they said user credentials in the request body works with Token endpoint.. Here is the link about it..
https://github.com/spring-projects/spring-security-oauth/issues/260
As per the above it seems I am missing something..
Any help/hint would be appreciated..

WCF RIA Services Form Authentication ServiceContext.User.Identity.Name Empty

We are using WCF Ria Services with silverlight 5 project.For authentication we are using Custom membership provider.WCF Ria Service in RIA Services class library.
Client side authentication running.We access current user name with WebContext.Current.User.Name.But server side ServiceContext.User empty.If we use [RequireAuthentication] attr. in DomainServices return always Access Denied.
How Can we push WebContext.Current.user to ServiceContext.user.I read several document and tutorial but every test fail.
Code examples :
CustomMembershipProvider.cs:
public class CustomMembershipProvider : MembershipProvider {
public override bool ValidateUser(string username, string password)
{
using (var context = new TimEntities())
{
var user = context.User.FirstOrDefault(u => u.Username == username &&
u.Password == password);
return user != null;
}
}
}
AuthenticationDomainService:
[EnableClientAccess]
public class AuthenticationDomainService : AuthenticationBase<AuthUser>
{}
public class AuthUser : UserBase
{}
App.Xaml.Cs:
var webContext = new WebContext();
var formsAuth = new FormsAuthentication();
var authContext = new AuthenticationDomainContext();
formsAuth.DomainContext = authContext;
webContext.Authentication = formsAuth;
ApplicationLifetimeObjects.Add(webContext);
I was having the same problem, but I have finally tracked down the answer. I had been messing about trying to expose SOAP endpoints, so I could access the same RIA services from a phone app. As part of that, I had added the following line in the App constructor:
WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
Get rid of that, and you should have the user name available again. SOAP endpoints still seem to be exposed to the phone app as well.
At first you have to configure Forms authentication for your hosting website regardless whether you use WCF RIA Service or not. Moreover, Forms authentication have to be installed and enabled on IIS.
Then you have to configure ASP.NET membership in order to use your CustomMembershipProvider class.

Make Programmatic login without username/password?

Greetings all
i am using the following method to make programmatic login for the user, but with his username & password, and it works fine:
public static void autoLogin(User user, HttpServletRequest request,
AuthenticationManager authenticationManager) {
GrantedAuthority[] grantedAuthorities = new GrantedAuthority[] { new GrantedAuthorityImpl(
user.getAuthority()) };
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
user.getUserName(), user.getPassword(),
grantedAuthorities);
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager
.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
// setting role to the session
request
.getSession()
.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
and i was wondering if it's possible to make programmatic login but without the username or the password authentication, just makes this user authenticated.
You can create your own subclass of Authentication, implement an AuthenticationProvider that supports it and configure authentication manager to use this provider.
(Actually, you can simply put a custom Authentication that always returns true from isAuthenticated() into SecurityContext, but this approach bypasses AuthenticationManager, so, for example, AuthenticationSuccessEvent wouldn't be published).
managed to do it by removing those lines
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager .authenticate(token);

Resources