I am registering a Servlet in an OSGi bundle using the HttpService. I have created my own HttpContext class which handles the security - BasicAuthentication and check against ActiveDirectory.
Dictionary<String, String> params = new Hashtable<String, String>();
params.put("jersey.config.server.provider.classnames", SettingsService.class.getName());
HttpContext ctx = new HttpContext()
{
#Override
public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException
{
// validation against Active Directory here
return ADAuth.authenticate(request, response);
}
#Override
public URL getResource(String name)
{
return null;
}
#Override
public String getMimeType(String name)
{
return null;
}
};
httpService.registerServlet("/rest", new MyServlet(), params, ctx); //$NON-NLS-1$
httpService.registerResources("/web", "/web", null);
So far so good. I would now like to set roles for the logged-in used so that I can use the #RolesAllowed annotation. The roles will depend on Active Directory groups.
How do I set the roles? I have tried setting roles using
HttpSession session = request.getSession(true);
Subject subject = (Subject) session.getAttribute("javax.security.auth.subject");
if (subject == null) {
subject = new Subject();
subject.getPrincipals().add(new PlainRolePrincipal(groupName));
session.setAttribute("javax.security.auth.subject", subject);
}
but request.isUserInRole always returns false.
Update
When I step into request.isUserInRole I eventually get to this code:
if (_authentication instanceof Authentication.Deferred)
setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
if (_authentication instanceof Authentication.User)
return ((Authentication.User)_authentication).isUserInRole(_scope,role);
return false;
The _authentication value is null. When / where should this be set?
You only created a new Subject instance. This does not automatically update the one in the session.
Apart from that the problem in jaas is always that there is not standard for role principals. You chose to encode the Role as PlainRolePrincipal. I am not sure this is what request is checking for. You will have to look into that code to see how it determines if a principal is a role principal or not.
A typical case is that it checks for a certain class or interface name but I am not sure which in your case.
Related
I have a Spring Boot (2.3.6.RELEASE) service that is acting as a resource server, it has been implemented using Webflux, client jwts are provided by a third party identity server.
I am attempting to test the security of the endpoints using JUnit 5 and #SpringBootTest. (For the record security appears to work as required during manual testing)
I am mutating the WebTestClient to include a JWT with an appropriate claim (myClaim), however in my custom ReactiveAuthorizationManager there is no bearer token in the requests header, thus with nothing to decode or claim to validate the request fails authorisation, as it should.
My test setup is thus:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
class ControllerTest {
#Autowired
private ApplicationContext applicationContext;
private WebTestClient webTestClient;
#BeforeEach
void init() {
webTestClient = WebTestClient
.bindToApplicationContext(applicationContext)
.apply(springSecurity())
.configureClient()
.build();
}
#Test
void willAllowAccessForJwtWithValidClaim() {
webTestClient.mutateWith(mockJwt().jwt(jwt -> jwt.claim("myClaim", "{myValue}")))
.get()
.uri("/securedEndpoint")
.exchange()
.expectStatus()
.isOk();
}
}
I have been attempting to follow this guide
I have tried the client with and without .filter(basicAuthentication()) just in case :)
It appears to me that the mockJwt() isint being put into the requests Authorization header field.
I also think that the ReactiveJwtDecoder being injected into my ReactiveAuthorizationManager will attempt to decode the test JWT against the identity provider which will fail.
I could mock the ReactiveAuthorizationManager or the ReativeJwtDecoder.
Is there anything I am missing?
Perhaps there is a way to create "test" JWTs using the Identity Services JWK set uri?
Additional detail:
Details of the ReactiveAuthorizationManager and Security Config
public class MyReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private static final AuthorizationDecision UNAUTHORISED = new AuthorizationDecision(false);
private final ReactiveJwtDecoder jwtDecoder;
public JwtRoleReactiveAuthorizationManager(final ReactiveJwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
#Override
public Mono<AuthorizationDecision> check(final Mono<Authentication> authentication, final AuthorizationContext context) {
final ServerWebExchange exchange = context.getExchange();
if (null == exchange) {
return Mono.just(UNAUTHORISED);
}
final List<String> authorisationHeaders = exchange.getRequest().getHeaders().getOrEmpty(HttpHeaders.AUTHORIZATION);
if (authorisationHeaders.isEmpty()) {
return Mono.just(UNAUTHORISED);
}
final String bearer = authorisationHeaders.get(0);
return jwtDecoder.decode(bearer.replace("Bearer ", ""))
.flatMap(jwt -> determineAuthorisation(jwt.getClaimAsStringList("myClaim")));
}
private Mono<AuthorizationDecision> determineAuthorisation(final List<String> claimValues) {
if (Objects.isNull(claimValues)) {
return Mono.just(UNAUTHORISED);
} else {
return Mono.just(new AuthorizationDecision(!Collections.disjoint(claimValues, List.of("myValues")));
}
}
}
#EnableWebFluxSecurity
public class JwtSecurityConfig {
#Bean
public SecurityWebFilterChain configure(final ServerHttpSecurity http,
final ReactiveAuthorizationManager reactiveAuthorizationManager) {
http
.csrf().disable()
.logout().disable()
.authorizeExchange().pathMatchers("/securedEndpoint").access(reactiveAuthorizationManager)
.anyExchange().permitAll()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
Loosely speaking, it turns out that what I am actually doing is using a custom claim as an "Authority", that is saying "myClaim" must contain a value of "x" to allow access to a given path.
This is a little different to the claim being a simple custom claim, i.e. an additional bit of data (a users preferred colour scheme perhaps) in the token.
With that in mind I realised that the behaviour I was observing under testing was probably correct, so instead of implementing a ReactiveAuthorizationManager I choose to configure a ReactiveJwtAuthenticationConverter:
#Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
final JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix(""); // 1
converter.setAuthoritiesClaimName("myClaim");
final Converter<Jwt, Flux<GrantedAuthority>> rxConverter = new ReactiveJwtGrantedAuthoritiesConverterAdapter(converter);
final ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(rxConverter);
return jwtAuthenticationConverter;
}
(Comment 1; The JwtGrantedAuthoritiesConverter prepends "SCOPE_" to the claim value, this can be controlled using setAuthorityPrefix see)
This required a tweak to the SecurityWebFilterChain configuration:
http
.csrf().disable()
.logout().disable()
.authorizeExchange().pathMatchers("securedEndpoint").hasAnyAuthority("myValue)
.anyExchange().permitAll()
.and()
.oauth2ResourceServer()
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter));
Tests
#SpringBootTest
class ControllerTest {
private WebTestClient webTestClient;
#Autowired
public void setUp(final ApplicationContext applicationContext) {
webTestClient = WebTestClient
.bindToApplicationContext(applicationContext) // 2
.apply(springSecurity()) // 3
.configureClient()
.build();
}
#Test
void myTest() {
webTestClient
.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("myValue"))) // 4
.build()
.get()
.uri("/securedEndpoint")
.exchange()
.expectStatus()
.isOk()
}
}
To make the tests "work it appears that the WebTestClient needs to bind to the application context (at comment 2).
Ideally I would have prefered to have the WebTestClient bind to the server, however the apply(springSecurity()) (at comment 3) doesnt return an appropriate type for apply when using bindToServer
There are a number of different ways to "mock" the JWT when testing, the one used (at comment 4) for alternatives see the spring docs here
I hope this helps somebody else in the future, security and OAuth2 can be confusing :)
Thanks go #Toerktumlare for pointing me in the direction of useful documentation.
In my current application, I am using Service Stack with JWT's for security. Security has been implemented and works perfectly. Trouble is, I would like to secure one route differently from the others. There is a document the logged in user retrieves, I want to make sure the document they are retrieving is theirs and not someone else's. It is very sensitive data. I would like to secure it differently because something like PostMan could be used with a valid token to retrieve any document, I want to prevent this. The users id is in the token, I would like to match it against the document that is being retrieved if possible. The current security is implemented like so:
public class AppHost: AppHostBase
{
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JsonWebTokenAuthProvider("myKey", "myAudience"),
}));
}
}
JsonWebTokenAuthProvider is a custom class where security was implemented, this all works perfectly. Here is the code:
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
// first validate the token, then get roles from session
string header = request.oauth_token;
// if no auth header, 401
if (string.IsNullOrEmpty(header))
{
throw HttpError.Unauthorized(MissingAuthHeader);
}
string[] headerData = header.Split(' ');
// if header is missing bearer portion, 401
if (!string.Equals(headerData[0], "BEARER", StringComparison.OrdinalIgnoreCase))
{
throw HttpError.Unauthorized(InvalidAuthHeader);
}
// swap - and _ with their Base64 string equivalents
string secret = SymmetricKey.Replace('-', '+').Replace('_', '/');
string token = headerData[1].Replace("\"", "");
// set current principal to the validated token principal
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(token, secret, Audience, true, Issuer);
string lanId = GetLanID(Thread.CurrentPrincipal.Identity.Name);
string proxyAsLanId = request.Meta.ContainsKey(META_PROXYID) ? request.Meta[META_PROXYID] : null;
if (HttpContext.Current != null)
{
// set the current request's user the the decoded principal
HttpContext.Current.User = Thread.CurrentPrincipal;
}
// set the session's username to the logged in user
session.UserName = Thread.CurrentPrincipal.Identity.Name;
session.Roles = GetApplicableRoles(lanId, proxyAsLanId);
authService.Request.SetItem("lanID", lanId);
authService.Request.SetItem("proxyAsLanId", proxyAsLanId);
return OnAuthenticated(authService, session, null, null);
}
I looked up RequestFilterAttribute found here, but I do not think that is what I want. Ideally, if the check fails I would like to return a 401 (unauthorized) if possible.
What is the best way to do this?
If you just want to handle one route differently than you can just add the validation in your single Service, e.g:
public object Any(MyRequest dto)
{
var lanId = base.Request.GetItem("lanId");
if (!MyIsValid(lanId))
throw HttpError.Unauthorized("Custom Auth Validation failed");
}
You could do the same in a RequestFilter, e.g:
public class CustomAuthValidationAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object responseDto)
{
var lanId = req.GetItem("lanId");
if (!MyIsValid(lanId))
{
res.StatusCode = (int) HttpStatusCode.Unauthorized;
res.StatusDescription = "Custom Auth Validation failed";
res.EndRequest();
}
}
}
And apply it to a single Service:
[CustomAuthValidation]
public object Any(MyRequest dto)
{
//...
}
Or a collection of Services, e.g:
[CustomAuthValidation]
public class MyAuthServices : Service
{
public object Any(MyRequest1 dto)
{
//...
}
public object Any(MyRequest2 dto)
{
//...
}
}
i wrote a normal html login form, that forwards to a vaadin project, where i want to receive the username and password and check if its valid. but i have problems getting this request.
when i add a requesthandler in the init() method of my UI class, i can only get the request data after the second call of the vaadin page (because at the first call of init, the hander ist not added yet)
#Override
protected void init(VaadinRequest vaadinRequest) {
setContent(new MainComponent());
VaadinSession.getCurrent().addRequestHandler(
new RequestHandler() {
#Override
public boolean handleRequest(VaadinSession vaadinSession, VaadinRequest vaadinRequest, VaadinResponse vaadinResponse) throws IOException {
String username = vaadinRequest.getParameter("username");
return false;
}
});
so i tried to overwrite the VaadinServlet method doPost, but it does not get triggered. when i overwrite the methode service(HttpServletRequest request, HttpServletResponse response), this method is triggered a serval times for each request, so also not a good place to get just the userdata.
so whats the right way to solve this problem?
i dont't know if this is the best solution, but at least it works. maybe this helps someone.
here a short explanation what i do. i retrieve the posted username and password from the post values of my plain html login formular from another url and see if it is existing in the database. if it exists, it returns the result, otherwise the value ERROR.
i extended the VaadinServlet and overwrote the method service like this
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.service(request, response);
String username = request.getParameter("username");
if(username != null) { // called several times, only set when username is returned, otherwise the value remains "error"
String password = request.getParameter("password");
this.result = getResult(username, Encrypter.encryp(password));
}
}
and this is inside my class extended from UI
#Override
protected void init(VaadinRequest vaadinRequest) {
MyServlet myServlet = (MyServlet) VaadinServlet.getCurrent();
String result = myServlet.getResult();
if(result .equals(MyServlet.ERROR)){ // check if the result set in the servlet is valid, otherwise forward to the loginpage
goToLogin();
myServlet.resetResult();
return;
}
myServlet.resetResult();
...
}
To whom it may concern - obtaining request and response in Vaadin 8 (which might be also available in Vaadin 7):
VaadinServletRequest vsRequest = (VaadinServletRequest) VaadinService.getCurrentRequest ();
HttpServletRequest httpServletRequest = vsRequest.getHttpServletRequest ();
VaadinServletResponse vsResponse = (VaadinServletResponse) VaadinService.getCurrentResponse ();
HttpServletResponse httpServletResponse = vsResponse.getHttpServletResponse ();
You can read the request parameter directly through the VaadinRequest object that's passed into init():
#Override
protected void init(VaadinRequest vaadinRequest) {
setContent(new MainComponent());
String username = vaadinRequest.getParameter("username");
}
It work for me perfect:
User is my simple class with username, name etc.
setting logged user in session:
public void setLoggedUser(User loggedUser) {
this.loggedUser = loggedUser;
getUI().getSession().getSession().setAttribute("loggedUser", loggedUser);
}
reading user:
loggedUser = (User) getUI().getSession().getSession().getAttribute("loggedUser"); //return null if not logged in
I'm using forms authentication in my MVC application. This is working fine. But not I want to adjust authorization to only allow people in certain roles. The logins correspond to users in active directory and the roles correspond to the groups the users are in.
For authentication, I simply call FormsAuthentication.SetAuthCookie(username, true) after verifying the login.
For authorizing, I first applied the attribute to the controllers I want to secure
[Authorize(Roles = "AllowedUsers")]
public class MyController
...
Next, I'm handling the OnAuthenticate event in global.asax.
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
// Create WindowsPrincipal from username. This adds active directory
// group memberships as roles to the user.
args.User = new WindowsPrincipal(new WindowsIdentity(ticket.Name));
FormsAuthentication.SetAuthCookie(ticket.Name, true);
}
catch (Exception e)
{
// Decrypt method failed.
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " + "supported for this application.");
}
}
With this when someone accesses the website they get the login screen. From there they can actually log in. However, somehow it doesn't save the auth cookie and they get a login screen after the next link they click. I tried adding a call to SetAuthCookie() in OnAuthenticate() but they made no difference.
Before I added this event handler to handle authorization, authentication worked fine. So somewhere in the framework User is being set. I'm wondering if this the correct approach and I'm just missing something or if I need a different approach.
What do I need to do to get this to work?
Thanks,
Scott
It seems like my initial approach won't work. I was trying to get ASP.NET to automatically load user roles from their AD account. No comment was given on whether this was possible. However, the research I've done indicates I'll have to write code to load AD group memberships into user roles.
The solution to creating the user principal that ASP.NET MVC uses appears to be to create it in FormsAuthentication_OnAuthenticate() and assign it to Context.User. It appears if I don't set Context.User ASP.NET MVC creates a user principal based off the auth ticket after FormsAuthentication_OnAuthenticate() returns. Additionally, ASP.NET MVC appears to do nothing with Context.User if I set it in FormsAuthentication_OnAuthenticate().
The following is what I ended up doing.
This is the code that handles authentication
public ActionResult LogOn(FormCollection collection, string returnUrl)
{
// Code that authenticates user against active directory
if (authenticated)
{
var authTicket = new FormsAuthenticationTicket(username, true, 20);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = DateTime.Now.AddMinutes(30);
Response.Cookies.Add(authCookie);
if (Url.IsLocalUrl(returnUrl)
&& returnUrl.Length > 1
&& returnUrl.StartsWith("/", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("//", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("/\\", StringComparison.OrdinalIgnoreCase))
{
return Redirect(returnUrl);
}
else
{
return Redirect("~/");
}
}
return View();
}
I initially tried just calling FormsAuthentication.SetAuthCookie(username, true) instead of manually creating, encrypting, and adding it to the Response cookie collections. That worked in the development environment. However, it didn't after I published to the website.
This is the log off code
public ActionResult LogOff()
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
authCookie.Expires = DateTime.Today.AddDays(-1);
}
Response.Cookies.Add(authCookie);
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
FormsAuthentication.SignOut() doesn't seem to do anything after I switched to manually creating, encrypting, and adding the auth ticket to the response cookie collection in the logon code. So I had to manually expire the cookie.
This is the code I have for FormsAuthentication_OnAuthenticate()
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || string.IsNullOrWhiteSpace(authCookie.Value))
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
UserData userData = null;
if (Application["UserData_" + authTicket.Name] == null)
{
userData = new UserData(authTicket.Name);
Application["UserData_" + authTicket.Name] = userData;
}
else
{
userData = (UserData)Application["UserData_" + authTicket.Name];
}
Context.User = new GenericPrincipal(new GenericIdentity(authTicket.Name), userData.Roles);
}
UserData is a class I created to handle caching user roles. This was needed because of the time it takes for active directory to return the group memberships the user belongs to. For completeness, the following is the code I have for UserData.
public class UserData
{
private int _TimeoutInMinutes;
private string[] _Roles = null;
public string UserName { get; private set; }
public DateTime Expires { get; private set; }
public bool Expired { get { return Expires < DateTime.Now; } }
public string[] Roles
{
get
{
if (Expired || _Roles == null)
{
_Roles = GetADContainingGroups(UserName).ToArray();
Expires = DateTime.Now.AddMinutes(_TimeoutInMinutes);
}
return _Roles;
}
}
public UserData(string userName, int timeoutInMinutes = 20)
{
UserName = userName;
_TimeoutInMinutes = timeoutInMinutes;
}
}
Roles can also be stored in a cookie and you have at least two options:
a role provider cookie (another cookie that supports the forms cookie), set with cacheRolesInCookie="true" on a role provider config in web.config. Roles are read the first time authorization module asks for roles and the cookie is issued then
a custom role provider that stores roles in the userdata section of the forms cookie, roles have to be added to the user data section of the forms cookie manually
The Authorization module asks the current principal for user roles, which, if role provider is enabled, either scans the role cookie (the first option) or fires the custom role provider methods.
Yet another, recommended approach is to switch to the Session Authentication Module (SAM) that can replace forms authentication. There are important pros, including the fact that SAM recreates ClaimsPrincipal out of the cookie and roles are just Role claims:
// create cookie
SessionAuthenticationModule sam =
(SessionAuthenticationModule)
this.Context.ApplicationInstance.Modules["SessionAuthenticationModule"];
ClaimsPrincipal principal =
new ClaimsPrincipal( new GenericPrincipal( new GenericIdentity( "username" ), null ) );
// create any userdata you want. by creating custom types of claims you can have
// an arbitrary number of your own types of custom data
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role1" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role2" ) );
var token =
sam.CreateSessionSecurityToken(
principal, null, DateTime.Now, DateTime.Now.AddMinutes( 20 ), false );
sam.WriteSessionTokenToCookie( token );
From now on, the identity is stored in a cookie and managed automatically and, yes, the Authorization attribute on your controllers works as expected.
Read more on replacing forms module with SAM on my blog:
http://www.wiktorzychla.com/2012/09/forms-authentication-revisited.html
I have a custom claim that determines language user will be using.
I have extension to ClaimsPrincipal, that checks for that claim and generates appropriate culture:
public static CultureInfo Culture(this ClaimsPrincipal principal)
{
Claim claim = principal.FindFirst(CustomClaimTypes.Language);
if (claim == null)
return CultureInfo.CreateSpecificCulture("en");
else
return CultureInfo.CreateSpecificCulture(claim.Value);
}
I've also implemented custom UserNameSecurityTokenHandler and custom ClaimsAuthorizationManager and both work fine. Actually entire solution works just fine, except for the fact that I'm unable to find appropriate point in WCF pipeline where I can set Thread.CurrentCulture based on the extension above.
I've tried ICallContextInitializer, IDispatchMessageInspector, but none of the above can actually see my ClaimsPrincipal (it gets overriden with empty principal). However, in my service operations, ClaimsPrincipal.Current points properly to identity I've created in UserNameSecurityTokenHandler.
Where should I set this Claim-dependant culture?
For WCF data service i use ProcessingRequest event:
public class ConfigurationManagementDataService : SecurityDataService<ConfigurationContext>
{
public ConfigurationManagementDataService()
{
Database.SetInitializer<ConfigurationContext>(null);
ProcessingPipeline.ProcessingRequest += OnProcessingRequest;
}
public void OnProcessingRequest(object sender, DataServiceProcessingPipelineEventArgs dataServiceProcessingPipelineEventArgs)
{
//assign IPrincipal to a Thread.Principal
//we must set both, but Thread.CurrentPrincipal is a main
if (context != null)
{
context.User = principal;
}
Thread.CurrentPrincipal = principal;