What is #AuthenticationPrincipal alternative for micronaut? - java

I'm trying to get UserDetails object like below. But, I have some difficulties and impossible to get UserDetails object, so there is only JSONObject in authentication.getAttributes(). Is there any alternative way in micronaut to get UserDetails object?
Custom UserDetails object:
public class MyUserPrincipal implements UserDetails {
private Account account;
public MyUserPrincipal(Account account) {
this.account = account;
}
public Account getAccount() {
return getAccount();
}
}
Rest api:
//micronaut
#Post(value = "/echo")
#Status(HttpStatus.OK)
public Long echo(#Nullable Authentication authentication) {
Long accountId = (Long)((JSONObject)authentication.getAttributes().get("account")).get("id");
return accountId;
}
For example in Spring Security it is easy with #AuthenticationPrincipal annotation in parameter.
Rest api:
#GET
public ResponseEntity<?> echo(#AuthenticationPrincipal MyUserPrincipal user) {
return new ResponseEntity<>(user.getAccount().getAccountId(), HttpStatus.OK);
}

If you are still looking for a solution, here is what works.
You have to provide an implementation of JwtAuthenticationFactory and replace default DefaultJwtAuthenticationFactory.
Something like this (code below is in Kotlin):
#Singleton
#Replaces(bean = DefaultJwtAuthenticationFactory::class)
class CustomJwtAuthenticationFactory() : JwtAuthenticationFactory {
override fun createAuthentication(token: JWT?): Optional<Authentication> {
try {
val builder = JWTClaimsSet.Builder()
builder.claim("username", token?.jwtClaimsSet?.getStringClaim("username"))
return Optional.of(AuthenticationJWTClaimsSetAdapter(jwtClaims))
} catch (e: Exception) {
throw RuntimeException("ParseException creating authentication", e)
}
}
}
All claims added using the builder will get added in the Authentication object and can be accessed in any controller eg:
#Get("/hello-world")
fun hello(authentication: Authentication): String =
authentication["username"] as String
If you are using Kotlin, use could also add extension methods on Authentication method to fetch attributes that you add to Authentication class eg:
fun Authentication.username(): String = this.attributes["username"]
Note: username is just an example. It is available as name instance variable on instance of Authentication.

UserDetails does not exist after authentication. The only object available is the Authentication. If you want to standardize the code you have above you could create a bean that handles injection of that specific property.
You could use an annotation to designate the injection by creating an annotation along with an implementation of AnnotatedRequestArgumentBinder. Something like the following:
public class Temp implements AnnotatedRequestArgumentBinder<YourAnnotation, Long> {
#Override
public Class<YourAnnotation> getAnnotationType() {
return YourAnnotation.class;
}
#Override
public BindingResult<Long> bind(ArgumentConversionContext<Long> context, HttpRequest<?> source) {
if (source.getAttributes().contains(OncePerRequestHttpServerFilter.getKey(SecurityFilter.class))) {
final Optional<Authentication> authentication = source.getUserPrincipal(Authentication.class);
if (authentication.isPresent()) {
return () -> (Long)((JSONObject)authentication.getAttributes().get("account")).get("id");
}
}
return ArgumentBinder.BindingResult.EMPTY;
}
}

Related

How to enable #AuthenticationPrincipal argument in a DGS query

I'm working on a Spring Boot service that has both a REST controller and a Netflix DGS GraphQL component. REST methods are protected with Spring Security, and whenever the current username is required, I add a method argument using the #AuthenticationPrincipal annotation, which gives me access to the authenticated user info:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
#RestController
public class ActionController {
#GetMapping("/getActions")
public List<ActionResponse> getActions(#AuthenticationPrincipal UserDetails userDetails) {
return actionService.getActions(userDetails.getUsername());
}
}
Now I want the same functionality for GraphQL methods implemented through Netflix DGS. But when I try to use the #AuthenticationPrincipal argument (like in the first example) it always equals null. The workaround I found is to manually assign the userDetails from the SecurityContextHolder:
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsQuery;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
#DgsComponent
public class ActionDatafetcher {
#DgsQuery
public List<Map<String, Object>> actions(#AuthenticationPrincipal UserDetails userDetails) {
// The following line works well:
// userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = userDetails.getUsername(); // ===> NullPointerException here
return actionService.getActionsMap(username);
}
}
How can I get #AuthenticationPrincipal to work in a DgsComponent?
Even though Spring Security's AuthenticationPrincipalArgumentResolver is in the application context, it's not picked up by DGS by default. You can achieve this by implementing DGS' own ArgumentResolver and delegating its work to Spring's AuthenticationPrincipalArgumentResolver.
So all you need to create is this:
[Kotlin]
#Component
class DgsAuthenticationPrincipalArgumentResolver : ArgumentResolver {
private val delegate = AuthenticationPrincipalArgumentResolver()
override fun supportsParameter(parameter: MethodParameter): Boolean {
return delegate.supportsParameter(parameter)
}
override fun resolveArgument(parameter: MethodParameter, dfe: DataFetchingEnvironment): Any? {
val request = (DgsDataFetchingEnvironment(dfe).getDgsContext().requestData as DgsWebMvcRequestData).webRequest as NativeWebRequest
return delegate.resolveArgument(parameter, null, request, null)
}
}
[Java]
#Component
public class DgsAuthenticationPrincipalArgumentResolver implements ArgumentResolver {
private final AuthenticationPrincipalArgumentResolver delegate = new AuthenticationPrincipalArgumentResolver();
#Nullable
#Override
public Object resolveArgument(#NotNull MethodParameter parameter, #NotNull DataFetchingEnvironment dfe) {
DgsContext context = ((DataFetchingEnvironmentImpl) dfe).getContext();
DgsWebMvcRequestData requestData = (DgsWebMvcRequestData) context.getRequestData();
NativeWebRequest request = requestData == null ? null : (NativeWebRequest) requestData.getWebRequest();
return delegate.resolveArgument(parameter, null, request, null);
}
#Override
public boolean supportsParameter(#NotNull MethodParameter parameter) {
return delegate.supportsParameter(parameter);
}
}
Passing nulls on 2n and 4th parameters is OK because they have no usage within delegated resolveArgument as you can check here.

Logout with JAAS login module

The question is a little bit longer than expected. Below is the link to a similar one (3rd post) where I didn't find the answer satisfying.
TL;DR
I am trying to logout using the JAAS Login Module. Here is the brief structure of the project:
LoginService is responsible for instantiating LoginContext when a user wants to log in:
#Service
public class LoginService {
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = new LoginContext("Login", new JAASCallbackHandler(credentials));
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
The LoginController calls the method:
#RestController
#RequestMapping("/login")
public class LoginController {
private final LoginService loginService;
#Autowired
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
#PostMapping
public ResponseEntity<UserDTO> getUserDTOFrom(#Valid #RequestBody Credentials credentials) {
UserDTO userDTO = loginService.getUserDTOFrom(userForm);
// return response that depends on outcome in the login service
}
}
The issue arises when I want to logout previously logged in user. LoginContext is responsible for calling the logout method in the JAAS Login Module. For instance:
loginContext.logout();
The method in the JAAS Login Module:
public class JAASLoginModule implements LoginModule {
#Override
public boolean logout() {
subject.getPrincipals().remove(usernamePrincipal);
subject.getPrincipals().remove(passwordPrincipal);
return true;
}
}
I don't have the LoginContext in LogoutService and unable to completely clear the previously authenticated subject.
I tried to create a singleton bean to get the same instance of the LoginContext:
#Configuration
public class LoginContextBean {
#Lazy
#Bean
public LoginContext getLoginContext(Credentials credentials) throws LoginException {
System.setProperty("java.security.auth.login.config", "resources/configuration/jaas.config");
return new LoginContext("Login", new JAASCallbackHandler(credentials));
}
}
#Service
public class LoginService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LoginService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public UserDTO getUserDTOFrom(Credentials credentials) {
try {
LoginContext loginContext = loginContextProvider.getObject(credentials);
loginContext.login();
// construct UserDTO object.
} catch (LoginException e) {
LOGGER.error("Login Exception: {}", e.getMessage());
// construct UserDTO object.
}
// return UserDTO object.
}
}
#Service
public class LogoutService {
private final ObjectProvider<LoginContext> loginContextProvider;
#Autowired
public LogoutService(ObjectProvider<LoginContext> loginContextProvider) {
this.loginContextProvider = loginContextProvider;
}
public void performLogout() {
LoginContext loginContext = loginContextProvider.getObject();
try {
loginContext.logout();
} catch (LoginException e) {
LOGGER.error("Failed to logout: {}.", e.getMessage());
}
}
}
The solution is not particularly useful, since next / the same user to log in will get NPE on the LoginContext.
I read that HttpServletRequest's getSession().invalidate(); suppose to call the logout() of JAAS or that HttpServletRequest's logout() would do the job. But both methods have no effect. For instance:
#RestController
#RequestMapping("/logout")
public class LogoutController {
private final LogoutService logoutService;
#Autowired
public LogoutController(LogoutService logoutService) {
this.logoutService = logoutService;
}
#DeleteMapping
public ResponseEntity<Void> deleteJwt(#CookieValue("jwt_cookie") String jwtToken, HttpServletRequest request) throws ServletException {
request.getSession().invalidate(); // logout() is not called.
request.logout(); // logout() is not called.
return getResponse();
}
}
I want to get the hand on the previously created LoginContext when a user wants to log out but create a new one when another user tries to log in.
Please note that I am not using Spring Security.
EDIT:
One of the ideas was to use a singleton that will hold a Set of login contexts associated with the particular user. And then call and destroy them when the user logs out. A key for such a Set could be a JWT token or user id. After further thinking, it appeared to me that a user might have multiple sessions, and in this case, user id as a key will fail to serve its purpose. The second option is a JWT token, but there is a scenario when the future middleware will issue a new JWT token upon expiration, then my Set will have no way to return a valid login context.
After some research, my team decided that JAAS doesn't suit our needs. We are not using the complete functionality it has to offer, and it ties our hands rather than smoothing the developing process.
If you will encounter a similar issue, here is an explanation:
we are using WebSphere 8.5.5 that has the support of JAAS. It is possible to logout, but the price will be tying it to the application server. Considering that in our plans is to move from WebSphere, this implementation is not an option.
The link to one of such guides lies here.
There are two alternatives for the future:
Wrap it in Spring Security since it offers support for JAAS;
Replace the custom module entirely relying on Spring Security's
functionality.

Apply #RolesAllowed annotations to existing users stored in database

TLDR
There is a need in the Java Web Application I develop, to implement user distinction over the REST services. I know that there are some annotations (#RolesAllowed, #PermitAll, #DenyAll), that can describe which role can use the service. My question is, how can I match the roles from #RolesAllowed with user role enumaration, that user is a persisted object stored in a DB??
Many tutorials explain the #RolesAllowed, but I found none to match those roles, with already created ones
Explanation
How to validate a user, by checking his role (found by session ID) automatically? I know Jersey do this already, by registering RolesAllowedDynamicFeature.class. I already managed to check that this RolesAllowedDynamicFeature.class works, by putting the #DenyAll annotation into a method, and returned a 403 error.
First of all, let's start with what is a user. Users are DB Entities, using Ebean to persist them from and to Java objects. Here is a sample from a User.class:
/*
* A sample that describes the fields that I ask for and to understand the concept
*/
#Entity
#Table(name = "users")
public class User extends Model
{
#Id
#GeneratedValue
#Column(name = "id")
private long id;
#Enumerated(EnumType.STRING)
#Column(name = "role", nullable = false)
private UserRole role;
// contructors getters setters helper methods etc
/**
* Fetch a user from DB
*
* #param id the id to search for
* #return a Person.class object or may return null
*/
public static User getUserById(Long id)
{
return Ebean.find(User.class, id);
}
/* and here is the UserRole enum that define the roles every user can have */
public static enum UserRole
{
Administrator, User, Manager;
}
}
All above code, works fine, user are stored correctly and I can fetch them easily.
Every User, at login, is authenticated with a similar service and a ConnectedUser object is created with a unique session ID (using UUID) for each one.
After each service call, there is an authentication running, that Authenticates if a user can use this service by searching, if there is a connected user entry for this session (which is stored as a cookie):
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter
{
#Context
private HttpServletRequest request;
/**
* Authenticates a user's access with every request that is made via a token.
*
* #param requestContext The request that is sent to the server.
* #throws IOException
*/
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
boolean isValidated;
Cookie sessionCookie = null;
Cookie[] cookies = request.getCookies();
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("CookieName")) {
sessionCookie = cookie;
}
}
}
if (sessionCookie != null) {
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser(sessionCookie.getValue(), request.getRemoteAddr());
}
else {
MultivaluedMap pathParameters = requestContext.getUriInfo().getQueryParameters();
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser((String)pathParameters.getFirst("token"), request.getRemoteAddr());
}
if (!isValidated) {
LOGGER.warn("[Authorization filter] Unauthorized user.");
URI indexURI = URI.create("http://login.jsp");
requestContext.setRequestUri(indexURI);
}
}
}
Notes:
Note1: Most implementations suggest to apply the roles into web.xml file. This is not doable though I think, for my case.
Note2: Also, where is the right place to authorize a user to use a service? I found that I can create ContainerRequestFilter class, with #Priority(Priorities.AUTHORIZATION). Which is better to do?
I'm kinda lost here. I read many of the Q/As or samples out there, but nothing explain it thorough.
You should separate the concerns and do the authentication in one filter and authorization in one filter. Generally how this is accomplished is by setting the SecurityContext from inside the authentication filter, and then retrieving it from inside the authorization filter.
The SecurityContext has a isUserInRole method that you override. This method should be called in the authorization filter. Generally you will have the roles as a member of the SecurityContext, and just iterate the roles
static class MySecurityContext implements SecurityContext {
private final String[] userRoles;
public MySecurityContext(String[] roles, String user) {
this.userRoles = roles;
}
#Override
public Principal getUser() {
return new Principal() {
#Override
public String getName() {
return name;
}
}
}
public boolean isUserInRole(String role) {
for (String userRole: userRoles) {
if (role.equals(userRole) {
return true;
}
}
return false;
}
// more methods to override
}
In the authentication filter, you just call the requestContext.setSecurityContext method passing in the new SecurityContext.
In the authorization filter, you would get the #RolesAllowed annotation by using the ResourceInfo. For example
class AuthorizationFilter implement ContainerRequestContext {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext request) {
SecurityContext sc = request.getSecurityContext();
RolesAllowed anno = info.getResourceMethod().getAnnotation(RolesAllowed.class);
String rolesAllowed = anno.value();
for (role: rolesAllowed) {
if (sc.isUserInRole(role)) {
return;
}
}
request.abortWith(Response.status(403).build());
}
}
Or something along those lines.
If you are using Jersey2 though, you don't need to implement the authorization yourself. This is already implemented in the RolesAllowedDynamicFeature. You just need to register the feature with your application. It works the same as I mentioned previously; A SecurityContext is expected to be filled prior to reaching the authorization filter, the the filter will check the roles and either authorize or reject.
My implementation is different from that of peeskillet but it has worked for me.
The code implementing security might look something like this
public class ServiceFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext req) throws IOException {
//user name and password are obtained from the header
String auth = req.getHeaderString(HttpHeaders.AUTHORIZATION);
if(auth == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
//user name and password
String[] credentials = auth.substring(1, auth.length()-1).split(":");
String user = credentials[0];//user name
String password = credentials[1];//password
if(user == null || password == null)
throw new WebApplicationException(Status.UNAUTHORIZED);
ServiceSecurity ss = null;
//user name and password are hardcoded here but you better put them in a DB or file
if(user.equals("servUser") && password.equals("service"))
ss = new ServiceSecurity(new ServiceUser("servUser"));
else if(user.equals("servAdmin") && password.equals("admin"))
ss = new ServiceSecurity(new ServiceUser("servAdmin"));
else
throw new WebApplicationException(Status.UNAUTHORIZED);
req.setSecurityContext(ss);
}
}
The security context
import java.security.Principal;
import javax.ws.rs.core.SecurityContext;
public class ServiceSecurity implements SecurityContext {
private ServiceUser sUser;
public ServiceSecurity(ServiceUser sUser){
this.sUser = sUser;
}
#Override
public String getAuthenticationScheme() {
// TODO Auto-generated method stub
return SecurityContext.DIGEST_AUTH;
}
#Override
public Principal getUserPrincipal() {
// TODO Auto-generated method stub
return sUser;
}
#Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isUserInRole(String role) {
// TODO Auto-generated method stub
return sUser.getRole().equals(role) ? true : false;
}
}
The security context implementation class is initialized with a class implementing Principal (ServiceUser), which is used to get the role for this particular user.
import java.security.Principal;
public class ServiceUser implements Principal {
private String role;
public ServiceUser(String role){
this.role = role;
}
public ServiceUser(){
}
public String getRole(){
return role;
}
public void setRole(String role){
this.role = role;
}
public String getName(){
return "some name";
}
}
In this case, you'd add the following to your server method
#RolesAllowed("servUser")
The credentials (user name and password) should be supplied in the request header (as is evident from the filter method above)

How to retrieve custom User object in GAE endpoints?

I've just created my own custom authentication on my google app engine Java app. And it wasn't that much of a trouble as is the next thing I'm trying to do.
Authentication works fine but now I'm trying to add some additional fields to the default User object so that I wouldn't have to make so many calls to the server.
So what I've done so far is created a custom class that implements Authenticator. Based on whether the user is authenticated or not the authenticate method returns the User object or null. User object is then accessible to my API endpoints.
To extend my app functionality I've tried extending the default User object, making some new fields, and then passing it to endpoints. However, since the User object accessible by endpoints is not the same kind as the one I extended from I can't get the extra fields.
MyAuthenticator.java
import com.google.api.server.spi.auth.common.User;
public class MyAuthenticator implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
// some code
return new AuthUser(...)
}
AuthUser.java
import com.google.api.server.spi.auth.common.User;
public class AuthUser extends User {
private String newToken;
public AuthUser(String email) {
super(email);
}
public AuthUser(String id, String email) {
super(id, email);
}
public AuthUser(String id, String email, String newToken) {
super(id, email);
this.newToken = newToken;
}
public String getNewToken() {
return newToken;
}
}
UserEndpoint.java
import com.google.appengine.api.users.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
// ...
}
Notice different class imports.
I can't use AuthUser in UserEndpoint sth method because then API expects me to post that object with my call to server.
How can I pass extra data from authenticator to my endpoint method?
AppEngine docs say the injected types are the following:
com.google.appengine.api.users.User
javax.servlet.http.HttpServletRequest
javax.servlet.ServletContext
However, it doesn't mention com.google.api.server.spi.auth.common.User, but it works for sure. I just tried with AppEngine Java SDK 1.9.32. I don't know if it's a bug or feature.
So in UserEndpoint.java, you have to import com.google.api.server.spi.auth.common.User, then you can cast it to AuthUser.
import com.google.api.server.spi.auth.common.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
((AuthUser)user).getNewToken();
// ...
}

Spring MVC (RESTful API): Validating payload dependent on a path variable

Use Case:
let's design a RESTful create operation using POST HTTP verb - creating tickets where creator (assigner) specifies a ticket assignee
we're creating a new "ticket" on following location: /companyId/userId/ticket
we're providing ticket body containing assigneeId:
{
"assigneeId": 10
}
we need to validate that assigneeId belongs to company in URL - companyId path variable
So far:
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody Ticket newTicket, #PathVariable Long companyId, #PathVariable Long userId) {
...
}
we can easily specify a custom Validator (TicketValidator) (even with dependencies) and validate Ticket instance
we can't easily pass companyId to this validator though! We need to verify that ticket.assigneeId belongs to company with companyId.
Desired output:
ability to access path variables in custom Validators
Any ideas how do I achieve the desired output here?
If we assume that our custom validator knows desired property name, then we can do something like this:
Approach one:
1) We can move this getting path variables logic to some kind of a base validator:
public abstract class BaseValidator implements Validator {
#Override
public boolean supports(Class<?> clazz)
{
// supports logic
}
#Override
public void validate(Object target, Errors errors)
{
// some base validation logic or empty if there isn't any
}
protected String getPathVariable(String name) {
// Getting current request (Can be autowired - depends on your implementation)
HttpServletRequest req = HttpServletRequest((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (req != null) {
// getting variables map from current request
Map<String, String> variables = req.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return variables.get(name);
}
return null;
}
}
2) Extend it with your TicketValidator implementation:
public class TicketValidator extends BaseValidator {
#Override
public void validate(Object target, Errors errors)
{
// Getting our companyId var
String companyId = getPathVariable("companyId");
...
// proceed with your validation logic. Note, that all path variables
// is `String`, so you're going to have to cast them (you can do
// this in `BaseValidator` though, by passing `Class` to which you
// want to cast it as a method param). You can also get `null` from
// `getPathVariable` method - you might want to handle it too somehow
}
}
Approach two:
I think it worth to mention that you can use #PreAuthorize annotation with SpEL to do this kind of validation (You can pass path variables and request body to it). You'll be getting HTTP 403 code though if validation woudnt pass, so I guess it's not exaclty what you want.
You could always do this:
#Controller
public class MyController {
#Autowired
private TicketValidator ticketValidator;
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#RequestBody Ticket newTicket,
#PathVariable Long companyId, #PathVariable Long userId) {
ticketValidator.validate(newTicket, companyId, userId);
// do whatever
}
}
Edit in response to the comment:
It doesn't make sense to validate Ticket independently of companyId when the validity of Ticket depends on companyId.
If you cannot use the solution above, consider grouping Ticket with companyId in a DTO, and changing the mapping like this:
#Controller
public class MyController {
#RequestMapping(value="/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody TicketDTO ticketDto,
#PathVariable Long userId) {
// do whatever
}
}
public class TicketDTO {
private Ticket ticket;
private Long companyId;
// setters & getters
}

Categories