I have access token received in controller and I need to extract Principal from string access token. Without using Authentication in method argument since in this object will be different user. Simple decoding of token should help. Anyone know how to do that from just access token string?
Example
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") String accessToken) {
//extract Principal from accessToken variable
}
After some time I manage to get Principal from access token string.
#Autowired
private TokenStore tokenStore;
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") String accessToken) {
tokenStore.readAuthentication(accessToken).getPrincipal();
}
I don't know why you're sending another user's token in the request, which i find it dangerous cause access token contain sensible information ( credentials ). i advise you to change the way you identify the second user by creating something like action or identification token ( the schema you define will contain the id of the user and the information you want to send ).
in case you have another phylosophhy that you didn't mention and assuming the access token is a Jwt, you must first validate it, using the algorithm and the private key used to hash it.if it's a valid token, you can access its content.
#RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity create(Authentication authentication,#RequestParam("access_token") JwtAuthenticationToken accessToken) {
// validate your accessToken
// to access the token details
accessToken.getTokenAttributes().get(A_KEY_IN_YOUR_TOKEN)
}
check this class
Related
I am working on a Spring Boot microservice that generate a JWT token and I have the following doubt about the possibility to add additional information into the generated token.
Basically in my code I have done something like this (it works fine):
#Override
public UserDetails loadUserByUsername(String UserId) throws UsernameNotFoundException {
String ErrMsg = "";
if (UserId == null || UserId.length() < 2) {
ErrMsg = "Nome utente assente o non valido";
logger.warn(ErrMsg);
throw new UsernameNotFoundException(ErrMsg);
}
User user = this.GetHttpValue(UserId);
if (user == null) {
ErrMsg = String.format("User %s not found!!", UserId);
logger.warn(ErrMsg);
throw new UsernameNotFoundException(ErrMsg);
}
UserBuilder builder = null;
builder = org.springframework.security.core.userdetails.User.withUsername(user.getEMail());
builder.password(user.getPswd());
String[] operations = user.getUserTypes().stream()
.map(UserType::getOperations)
.flatMap(Set::stream)
.map(Operation::getName)
.distinct()
.toArray(String[]::new);
builder.authorities(operations);
return builder.build();
}
As you can see in the previous code I have this method returning a UserDetails object (belonging to Spring Security, it is this class: org.springframework.security.core.userdetails.UserDetails). This UserDetails instance was created starting from this retrieved model object (it was retrieved performing a call to a REST endpoint):
User user = this.GetHttpValue(UserId);
Basically this UserDetails object is used to generate the JWT token by this second method that uses the previous UserDetails object:
private String doGenerateToken(Map<String, Object> claims, UserDetails userDetails)
{
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.claim("authorities", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
.compact();
}
NOTE: the Jwts is io.jsonwebtoken.Jwts.
This generate a token containing the following payload information:
{
"sub": "xxx#gmail.com",
"exp": 1637412048,
"iat": 1637325648,
"authorities": [
"ADMIN"
]
}
It is correct but...is it possible to add some further information to this payload? In particular I need to add some information that are into my User DTO object but not into the previous Spring Security UserDetails instance.
I implemented this behavior following an Udemy tutorial and now I have the following doubt.
Why the previous doGenerateToken() method generate the token starting from the Spring Security UserDetails instance and not directly from the User DTO object (it contains more usefull information).
If there are some specific reason to use this UserDetails instance instead my simple User DTO object, exist a way to add these information to the UserDetails instance and then put these additional fields into my JWT token?
You are passing a map of claims to the doGenerateToken method. These are then added to the token payload as claims. You can add whatever you like to this map to create the token.
As for the question why the doGenerateToken method doesn't use the User DTO? Well, if that method is not part of an interface, then you can have it accept any class, right? If you prefer to work with the User object, then I think you can. If it's an implementation of an interface, then you should ask the authors what was the reason for that, but people rather have thought through those interfaces in Spring and there are good reasons why they are built this way or another. Even if at first they look weird.
Before everything I tried this two solution but didn't work for me
Equivalent of javax.ws.rs NameBinding in Micronaut?
https://blogs.ashrithgn.com/custom-annotation-to-handle-authorisation-in-micronaut-aop-tutorial/
In my application I have to get a string in the Authorization header and then decode it from base64 and the json transform it into a POJO. Certainly the string is a jwt and I need to decode the public part of the json to get a data from a field.
Technically speaking a client will forward the header to me to take it, decode it and extract the data. (It's very bad practice but that's what I have to do).
For this I am using micronaut 2.4.1 and this is my code:
Interceptor:
public class HeadInterceptor implements MethodInterceptor<Object, Object> {
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
Request request = (Request) context.getParameterValueMap().get("request");
// Where do i get Authorization header?
// i.e String token = (String) context.getParameterValueMap().get("Authorization");
String token = "eyJhdWQiOiJ0ZXN0IiwiaXNzIjoidGVzdCIsInN1YiI6InRlc3QiLCJleHAiOjExMTExMTEsImlhdCI6MTExMTExMTEsImRhdGEiOiJ0ZXN0In0=";
ObjectMapper mapper = new ObjectMapper();
Info info = mapper.readValue(new String(Base64.getDecoder().decode(token)), Info.class);
request.setData(info.getSub().toUpperCase());
return context.proceed();
}
}
Controller:
#Controller("/main")
public class MainController {
#Post
#Head
public Single<Response> index(#Body #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
}
Here's a sample app https://github.com/j1cs/micronaut-jacksonxml-error
(ignore the name is for other issue)
In your implementation, the header cannot be shown in the interceptor because your index method doesn't receive it as a parameter.
So, if you add it as a parameter as below:
...
#Post
#Head
public Single<Response> index(#Body #Valid Request request, #Header("Authorization") String authorizationHeader) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Then, you can retrieve it in the intercept method via getParameterValues(). Basically, it will be the second argument.
...
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
String token = (String) context.getParameterValues()[1];
...
}
...
Update
Since you want your Request to contain both body and header, I edited the solution a bit. Basically, the header is added as a member variable to Request as below:
public class Request {
#NotNull
#NotBlank
private String info;
private String data;
#Header("Authorization")
String authorizationHeader;
}
Then, use #RequestBean rather than a #Body annotation on your Request parameter:
...
#Post
#Head
public Single<Response> index(#RequestBean #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Finally, you can access the header easily in your intercept() method as follows:
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
Request request = (Request) context.getParameterValueMap().get("request");
String token = request.authorizationHeader;
...
}
I created a pull request for this change here, so you can check how it works.
In order to address the problem, you may first break the problem into parts.
Part 1: How to get arbitrary header (or list all headers)?
Try to use request.getHeaders() doc.
Part 2: How to get the header named Authorization ?
Use the way in part 1. In addition, be careful about the case. For example, is Authorization the same as authorization?
Method 2:
In controller (https://github.com/j1cs/micronaut-jacksonxml-error/blob/master/src/main/java/me/jics/MainController.java):
public Single<Response> index(#Body Request request, #Header('Authorization') String authorization) {
...
}
p.s. the "Header" annotation's doc is here: https://docs.micronaut.io/2.0.1/api/io/micronaut/http/annotation/Header.html
In interceptor:
...
String token = context.getParameterValueMap().get("authorization");
...
Why the code looks like this:
Firstly get the auth header you want using parameter injection.
Secondly, recall the fundamental concepts of AOP / AspectJ (which your interceptor class uses). Inside your interceptor, you intercept a method (in your case, the index method in controller. Thus, you can happily get the parameters of that method. In the code above, just the authorization parameter.
Please tell me if you are stuck on somewhere (and paste the code and the outputs).
So I basically have a controller method with a PreAuthorize annotation. By default the method will return all projects. The method signature also includes an optional query string (blank query means retrieve all records).
The issue is that if the logged in user is only supposed to view/manage his/her own records, the query string needs to include a filter in it such as "clientId:2".
Now to do that, I was thinking of using the Principal object to retrieve the logged in user and check if he/she is a client as well, then I update the query by adding the required filter to it.
I am just not sure if this is the best approach for this type of issues.
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
#RequestMapping(value = "/projects", method = RequestMethod.GET)
public ResponseEntity<RestResponse> list(Principal principal, #RequestParam(value = "query", required = false, defaultValue = "") String query) {
//If a client is logged in, he/she will have the MANAGE_OWN authority so will need to update the query string to include clientId:<logged-in-client-id>
I would rather move the #PreAuthorize to an application service.
class SomeApplicationService {
UserService userService;
SecurityService securityService;
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
public List<Project> getProjects(String clientId) {
User currentUser = userService.getLoggedInUser();
if(securityService.canManageAllProjects(currentUser))
//get all projects or projects of clientId
else if(securityService.canManageOwnProjects(currentUser))
//get own projects, ignore clientId
}
}
I'm trying to write a simple PUT request method in Spring MVC. I got the following:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
String name,
String email) {
User user = repository.findOne(id);
user.setName(name);
user.setEmail(email);
System.out.println(user.toString());
repository.save(user);
return user;
}
Which is obviously wrong, because it returns the following:
User{id=1, name='null', email='null'}
I also tried with #RequestBody annotation, but that also did not help. Any ideas what I'm doing wrong here would be greatly appreciated.
You can receive name and email whith the #RequestBody annotation:
#RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestBody User user) {}
This is a better practice when it comes to REST applications, as your URL becomes more clean and rest-style.
You can even put a #Valid annotation on the User and validate its properties.
On your postman client, you send the User as a JSON, on the body of your request, not on the URL. Don't forget that your User class should have the same fields of your sent JSON object.
See here:
You did not tell spring how to bind the name and email parameters from the request. For example, by adding a #RequestParam:
public #ResponseBody User updateUser(#PathVariable("id") long id,
#RequestParam String name,
#RequestParam String email) { ... }
name and email parameters will be populated from the query strings in the request. For instance, if you fire a request to /users/1?name=Josh&email=jb#ex.com, you will get this response:
User{id=1, name='Josh', email='jb#ex.com'}
In order to gain more insight about defining handler methods, check out the spring documentation.
I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.