Spring : Autowired object conflicted between users - java

I am creating a backend application using spring boot, whose users authenticate using a JWT.
when I validate the user's jwt (using a filter), I parse its content into an object that I named "authentication", containing the user's id, name, role...
after creating this object with user data, I autowire it, so that I use it to any class (service, controller...)
Most of the time I have no problem with it. but sometimes I find that this object contains another user's information.
I thought that each user has access to an object containing his information, and if this object does not contain information then he will have an empty object, and not an object of the other users
Can this reasoning create a conflict? and how to solve it?
This is my filter class
#Component
class AuthenticationFilter : Filter, Ordered
{
#Autowired
private lateinit var jwtUtils : JwtUtils
#Autowired
private lateinit var beanFactory : AutowireCapableBeanFactory
#Autowired
private lateinit var authentication : Authentication
override fun doFilter(request : ServletRequest, response : ServletResponse, chain : FilterChain)
{
if (request is HttpServletRequest)
{
val authFromHeader : Authentication? = getAuthenticatedUser(request)
if (authFromHeader != null)
{
authentication.id = authFromHeader.id
authentication.name = authFromHeader.name
authentication.role = authFromHeader.role
authentication.level = authFromHeader.level
beanFactory.autowireBean(authentication)
}
}
chain.doFilter(request, response)
}
private fun getAuthenticatedUser(request : HttpServletRequest) : Authentication?
{
return try
{
val jwtToken : String? = request.getHeader(HttpHeaders.AUTHORIZATION)
jwtUtils.getAuthentication(jwtToken)!!
}
catch (exception : Exception)
{
null
}
}
override fun getOrder() : Int
{
return Ordered.HIGHEST_PRECEDENCE
}
}

authFromHeader have to be declared in lateinit

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.

What is #AuthenticationPrincipal alternative for micronaut?

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;
}
}

Forward request headers to multiple service calls in Spring + Netflix + Feign

I have a bunch of intermediate and core services within my application. All services are Spring Boot and using Netflix Library. When a user requests information, the request will/might pass other services in the chain eg:
Client <-> Zuul <-> Service B <-> Service A
I have configured all services (A and B) to be ResourceServer so that every access needs to be authenticated. When requesting an access token (From a Spring Security Server) and use it to request information directly from Service A, everything works fine. When I use the same token to access information from Service B (which needs Service A down the line) I get an "HTTP 401: Full authentification is required" error. Service B uses a FeignClient to call Service A.
After some debugging, I found out, that the Authorization-Header is not passed from Service B to Service A. Service B checks the token itself correctly, grants access to the method and tries to perform the request of Service A.
I tried a RequestInterceptor but without any success (Error "Scope 'request' is not active for the current thread")
#Component
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
private final OAuth2ClientContext oauth2ClientContext;
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext) {
Assert.notNull(oauth2ClientContext, "Context can not be null");
this.oauth2ClientContext = oauth2ClientContext;
}
#Override
public void apply(RequestTemplate template) {
if (template.headers().containsKey(AUTHORIZATION_HEADER)) {
...
} else if (oauth2ClientContext.getAccessTokenRequest().getExistingToken() == null) {
...
} else {
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE,
oauth2ClientContext.getAccessTokenRequest().getExistingToken().toString()));
}
}
}
This is an example proxy function that uses the FeignClient:
#Autowired
private CategoryClient cat;
#HystrixCommand(fallbackMethod = "getAllFallback", commandProperties = {#HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2") })
#GetMapping("/category")
public ResponseEntity<List<Category>> getAll() {
try {
ResponseEntity<List<Category>> categories = this.cat.getAll();
...
return categories;
} catch(Exception e) {
...
}
}
Is there any working solution to pass the Authorization-Header from the proxy function to the FeignClient so that Service A will receive the header and can do its own auth check with it?
Found a working solution. I still don't know if this is the "best" way to do it and if anyone got a better solution I'd be happy if you share it. But for now, this is working as expected:
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
requestTemplate.header("Authorization", "Bearer " + details.getTokenValue());
}
};
}

springmvc validate httpsession containing userobject

I need to validate HttpSession (for Spring MVC Application) in a better way for my current Project.
Here is the Scenario:
1) Once user is successfully validated, userObject object is added to httpSession class
HttpSession session = req.getSession(true);
session.setAttribute(AppConstants.LOGGEDIN_PARAM, userDetail);
2) Then for each request, userObject is retrieved from HttpSession Class to validate user Session
#RequestMapping(value = "/apply", method = RequestMethod.GET)
public String getTourApplyPage(HttpServletRequest req, ModelMap map) {
UserDetailTO userDetail = (UserDetailTO) req.getSession().getAttribute(AppConstants.LOGGEDIN_PARAM);
Long employeeId = userDetail.getUserType() == 1 ? userDetail.getEmployeeId():userDetail.getUserId();
if (employeeId == 0) {
req.setAttribute(AppConstants.MSG_PARAM, "Invalid employee Id.");
return userDetail.getUserType() == 1 ? AppConstants.PIS_MESSAGE : AppConstants.ADMIN_PIS_MESSAGE;
}
...
}
There can be better approaches to set userDetail object inside HttpSession but I had a restriction to not change this implementation (Point 1).
Can it possible to change getting a better implementation for getting a userDetail object from HttpSession (Point 2)?
Is it possible to write a better implementation for getting a userDetail object from httpSession?
Working at such a high level of abstraction, as controllers are at, you don't necessarily need to inject neither an HttpServletRequest nor an HttpSession.
You can make your controller session-scoped and inject a session-scoped bean there. The bean can hold a userDetails and a message for failed validations.
#RestController
#Scope("session")
public class Controller {
#Autowired
private SessionDetails details;
#PostMapping(path = "/validate")
public void validate() {
details.setUserDetails(...);
}
#GetMapping(path = "/apply")
public String apply() {
final UserDetailTO userDetails = details.getUserDetails();
...
}
}
#Component
#Scope("session")
class SessionDetails {
private String message;
private UserDetailTO userDetails;
// getters & setters
}

Controller library direct access to HttpServletRequest

I have numerous controllers in my application that extend a central controller class. Currently, in every controller function, I have to pass the request into this function in order for my function to grab the current username. Is it possible for this controller class to get the request on it's own without requiring it as an extra parameter?
public class Controller {
protected String environment;
public Controller () {
}
public ModelAndView prepareModel (HttpServletRequest request, ModelAndView model) {
contentDao.clearExpiredLocks();
model.addObject("currentUser", contentDao.findUser(request.getRemoteUser()));
//define current environment
this.environment = (request.getRequestURL().indexOf("localhost") > 0) ? "dev" : "uat";
model.addObject("environment", this.environment);
You can get the current HttpServletRequest as follows:
HttpServletRequest request = (HttpServletRequest) RequestContextHolder
.currentRequestAttributes()
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
You can use this code in a method of your controller, or use it to expose request as a request-scoped bean and inject the corresponding scoped proxy as a field of your controller.
You can use something like this:
public abstract class AbstractController {
protected HttpServletRequest req
protected AbstractController(HttpServletRequest req) {
this.req = req
}
}
public class ConcreteController extends AbstractController {
protected ConcreteController(String name) {
super(name);
}
private void getUserName(){
this.req.getRemoteUser();
}
}
That's just one quick tip, I believe that there are more possibilities how to do that.
In my case, What I did is : I get user MainController.getLoginPerson() and use all user's info in all controllers. All controllers extends to MainController.
Here is method MainController.getLoginPerson():
MainController.getLoginPerson() {
// calls to authentication service method
authenticationService.getLoginPerson();
}
authenticationService.getLoginPerson() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return (UserPrincipalImpl) auth.getPrincipal();
} else {
return null;
}
}

Categories