I have few Spring-boot controller classes to expose few rest web-services. Whenever some user tries to access any of those services, I need to invoke an web-service to check whether the user (user id will be passed as RequestHeader) is authorized or not. If not authorised, need to display an error page (freemarker template) to the user.
I don't want to write a method which will invoke the authentication webservice and call that from each controller methods and throw an exception and redirect the user to the access denied error page using #ControllerAdvice as here I have to call the method from all controller methods.
I'm not sure whether I can use WebSecurityConfigurerAdapter/AuthenticationManagerBuilder to call the webservice and do the validation.
I'm looking for some solution where I would write an interceptor and spring-boot will invoke the webservice before calling the controller classes and will be able to redirect to the error page, if validation fails.
As a recommendation, take a few minutes for reading about Spring Security (https://projects.spring.io/spring-security/), you must configure it and probably you will spend more time than expected, anyway you have so much more profits than make security by ourself.
Benefits are things like:
#PreAuthorize("hasRole('ROLE_USER')")
On every place you can get the user logged through the SecurityContext with something like:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
The way SpringSecurity authenticate users is with JWT (JsonWebToken) this is a really nice way because you can pass and retrieve all information you want:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
And you can forget every possible problem (bad authentication, phishing, xss or csrf..) because it works with public/private key and secrets, so anyone can create a token.
Related
I have a setup of spring boot OAuth for AuthServer and it is resposible for serving a number of few resource server for authentication using spring-security-jwt.
My problem is while authenticating I need to load the roles of a user but specific to the clientId.
eg: If user1 have roles ROLE_A, ROLE_B for client1 and ROLE_C, ROLE_D for client2, then when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C, ROLE_D because I am getting roles based on username.
If I need to have a role based on the client then I need clientId.
FYI,
I am using the authorization code flow for authentication.
I have seen similar question but that is based on password grant but I am trying on authorization code flow and that solution doesn't work for me.
Password grant question link
Below is my code where I need clientId
MyAuthenticationProvider.java
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
String clientId = ? // how to get it
....
}
}
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) {
String clientId = ? // how to get it
....
}
}
You probably need to see OAuth2Authentication in Spring-security. When your client is authenticated by oauth2, then your "authentication" is actually instance of OAuth2Authentication that eventually implements Authentication.
If you see the implementation of OAuth2Authentication, it's done as below;
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication
.getPrincipal();
}
so if request included "clientId', then you should be able to get clientId by calling getPrincipal() and typecasting to String as long as your request didn't include user authentication.
For your 2nd case, username is actually considered as clientId. You need to call in-memory, RDBMS, or whatever implementation that has clientId stored and returns ClientDetails. You'll be able to have some idea by looking into Spring security's ClientDetailsUserDetailsService class.
Since I didn't get any appropriate solution for my question, I am posting the solution that I used after digging source code and research.
MyJwtAccessTokenConverter.java (Extend JwtAccessTokenConverter and implement enhance method)
public class OAuthServerJwtAccessTokenConverter extends JwtAccessTokenConverter {
....
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String clientId = authentication.getOAuth2Request().getClientId();
// principal can be string or UserDetails based on whether we are generating access token or refreshing access token
Object principal = authentication.getUserAuthentication().getPrincipal();
....
}
....
}
Info:
In enhance method, we will get clientId from authentication.getOAuth2Request() and userDetails/user_name from authentication.getUserAuthentication().
Along with JwtAccessTokenConverter, AuthenticationProvider and UserDetailsService are required for authentication in generating access token step and refresh token step respectively.
get authorization header from request then parse from base64 to get the client-id.
something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String authHeader = request
.getHeader("Authorization");
I am developing an app whose frontend is written using React.js and the backend REST API is written using the Spring framework. I wanted to add social logins to my website, so after days of googling and research, I understood that OAuth2 is the solution. I came to know that the frontend should handle getting the authorization token from the Resource Server(Facebook here) and my backend(java) should validate that token and connect with Facebook to get an access token. Then that access token should be stored in my database along with the user details(e.g email).
Here is my requirement, once the user clicks on the "Continue with Facebook" button, my app should create there account in my own database using details - email and Name(the signup feature). And later whenever they click on this button again, they will be logged in not sign up. The way other websites handle it.
As of now, I have the button working in my app, which brings me the authorization token from Facebook.
Can someone please guide me the path I should follow here.
Also, any special attention to some error handling I should follow.
Here's the general approach using Spring Boot as a REST API backed by Spring Data JPA and Spring Security that works for iOS and ember.js together. There's probably libraries and what not that you can use but I'm just going to outline the fundamental flow.
Your user object needs a one to one mapping to a facebook account. Best practice would involve encrypting the authToken before storing in the DB
#Entity
class FacebookAccount {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
Long id
String facebookUserId
String authToken
#OneToOne
#JoinColumn(name="user_id")
User user
}
#Entity
class User{
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
FacebookAccount facebookAccount
}
Use the facebook Javascript SDK to get a User Access Token and the User's Facebook User ID. You'll get a response back from facebook in your react app that looks like this in the successful case:
{
status: 'connected',
authResponse: {
accessToken: '...',
expiresIn:'...',
reauthorize_required_in:'...'
signedRequest:'...',
userID:'...'
}
}
Hit some login endpoint with the info received in step 2 like /login/facebook. I cannot predict how your app is structured. In my app, this code is handled by my Authentication Filter that implements GenericFilterBean. I pass a header X-Auth-Facebook with the token.
Verify the token. I'm doing this in a class that implements AuthenticationProvider within the Authentication authenticate(Authentication authentication) throws AuthenticationException method. This class will need your App's Access Token accessToken and the user's Token userAccessToken:
URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"))
builder.queryParam("access_token", accessToken)
builder.queryParam("input_token", userAccessToken)
URI uri = builder.build()
RestTemplate restTemplate = new RestTemplate()
JsonNode resp = null
try {
resp = restTemplate.getForObject(uri, JsonNode.class)
} catch (HttpClientErrorException e) {
throw new AuthenticationServiceException("Error requesting facebook debug_token", e)
}
Boolean isValid = resp.path("data").findValue("is_valid").asBoolean()
if (!isValid)
throw new BadCredentialsException("Token not valid")
String fbookUserId = resp.path("data").findValue("user_id").textValue()
if (!fbookUserId)
throw new AuthenticationServiceException("Unable to read user_id from facebook debug_token response")
// spring data repository that finds the FacebookAccount by facebook user id
FacebookAccount fbookAcct = facebookAccountRepository.findByFacebookUserId(fbookUserId)
if(!fbookAcct){
// create your user here
// save the facebook account as well
} else{
// update the existing users token
fbookAcct.authToken = userAccessToken
facebookAccountRepository.save(fbookAcct)
}
// finish the necessary steps in creating a valid Authentication
I, personally, then create a token that my client's use when accessing my API (rather than have them continue to pass the facebook token with all requests).
I also need more user provided information to create the user (a chosen username, agreeing to terms and conditions, etc). So my actual implementation throws an EntityNotFoundException instead of creating the user, which my clients then use to pop up a registration form that provides only the fields I cannot get from facebook. On submit of this from the client, I hit my /signup/facebook endpoint with the facebook token and what's needed to create my user. I fetch the profile from facebook and create the user (automatically logging them in the process).
Edit: If you want to use Spring 0Auth, you could follow the example for creating a Spring 2 Oauth Rest Template
#Bean
public OAuth2ProtectedResourceDetails facebook() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("facebook");
details.setClientId("233668646673605");
details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d");
details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token");
details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth");
details.setTokenName("oauth_token");
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.valueOf("text/javascript")));
template.setMessageConverters(Arrays.<HttpMessageConverter<?>> asList(converter));
return template;
}
and then in use:
public String photos(Model model) throws Exception {
ObjectNode result = facebookRestTemplate
.getForObject("https://graph.facebook.com/me/friends", ObjectNode.class);
ArrayNode data = (ArrayNode) result.get("data");
ArrayList<String> friends = new ArrayList<String>();
for (JsonNode dataNode : data) {
friends.add(dataNode.get("name").asText());
}
model.addAttribute("friends", friends);
return "facebook";
}
I took the above request for friends from the project. it shouldn't be hard to tailor the above code I showed with debug_token to use the Spring OAuth rest template. Hope this helps :)
I am now exploring that authentication of users in microservice. For that I am created my authentication service - checkUserAuthentication. Also providing Microservice also. this is already deployed in cloud.
Now I am creating new service with specific business logic. In this service , need to authenticate and check authorization of user to access this end-point by using authenticationProvider from spring security.
For this I am reading and exploring the following tutorials,
https://dzone.com/articles/spring-security-custom
http://roshanonjava.blogspot.in/2017/04/spring-security-custom-authentication.html
http://javasampleapproach.com/spring-framework/spring-security/spring-security-customize-authentication-provider
http://www.baeldung.com/spring-security-authentication-provider
In here they are implements AuthenticationProvider in class CustomAuthenticationProvider.
and in method they are receiving username and password is like following,
public Authentication authenticate(Authentication authentication) throws
AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
Optional<User> optionalUser = users.stream().filter(u -> u.index(name,
password)).findFirst();
if (!optionalUser.isPresent()) {
logger.error("Authentication failed for user = " + name);
throw new BadCredentialsException("Authentication failed for user = " + name);
}
// find out the exited users
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority(optionalUser.get().role));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(name, password,
grantedAuthorities);
logger.info("Succesful Authentication with user = " + name);
return auth;
}
These are codes from documentation. Instead of this method, I need to do in different way. Here I am adding my requirements:
My requirement: I need to receive username and password from API Request.And For checking this username and password, I need to call my deployed APIs checkUserAuthentication and checkUserAuthorization.
My doubts on this:
Can I directly call these API within "public Authentication authenticate(Authentication authentication)" method ?
How I receive username and password from the received request ?
Why we are using UsernamePasswordAuthenticationToken ? , If we are sending JWT token instead of username and password, then which class will use for providing reply?
Since I only started with Spring Security, I am new to security world.
Can I directly call these API within "public Authentication authenticate(Authentication authentication)" method ?
Yes.
How I receive username and password from the received request ?
Same as they are doing in authenticate method.
Why we are using UsernamePasswordAuthenticationToken ? , If we are sending JWT token instead of username and passowrd, then which class
will use for providing reply?
UsernamePasswordAuthenticationToken is used internally by spring security. This
comes into the picture when you create a session in spring. it contains the user information (eg. email etc.) and authorities (role).For example, when you receive a JWT token in your application, you will validate the JWT token (signature etc. ) and upon successfull validation of JWT, you will create an object of UsernamePasswordAuthenticationToken and spring will save it in session. For each incoming request, spring will call boolean isAuthenticated() method on this object to find if user can access the required resource.
Now when you have got all your answers, my recommendation is to go with Oauth2 for your boot microservices. there are plenty of example how to implement it and customize it for your requirement. (Basically, you have to implement your Authorization server which will authenticate the user with your service checkUserAuthentication and generate the accesstoken. Each consumer of your microservice needs to send this accesstoken which they have got from Authorization server and you need to validate it in your microservice. So your microservice will act as Resource Server).
Hope it will help.
I have 2 questions about spring jwt token?
The first one is related to the additional informations of the JWT token:
- Is there any way to hide the additional informations from the oauth2 jwt token because they are in plain text and the same informations are duplicated in the JWT access token or payload
public class CustomTokenEnhancer extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
User user = (User) authentication.getPrincipal();
additionalInfo.put("organization", user.getOwnerId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
}
The second one concerns the mapping of my user permissions to access token scopes, in fact, when i add the scopes as additional informations, which represent for my case the different permissions for a given user, and when I want to test this in my WS by #PreAuthorize("hasRole('ROLE_USER') and #oauth2.hasScope('XXXXX')") annotation. It does not work because the checking is based on client scopes rather than user access token scopes? Is there a way, for using access token scopes (which represents my permissions user) rather than client scopes by using the #oauth2.hasScope('XXXXX') annotation? how can i do that?
thanks.
I've been looking for a way to authenticate a user via REST controller (URL params).
The closest thing to do so is the following:
#Controller
#RequestMapping(value="/api/user")
public class UserController extends BaseJSONController{
static Logger sLogger = Logger.getLogger(UserController.class);
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(#RequestParam(value="username") String user, #RequestParam(value="password") String pass) throws JSONException {
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, pass);
MyCellebriteAuthenticationProvider MCAP = new MyCellebriteAuthenticationProvider();
if (MCAP.authenticate(userAuth) == null){
response.put("isOk", false);
}
else{
SecurityContextHolder.getContext().setAuthentication(userAuth);
response.put("isOk", true);
response.put("token", "1234");
}
return response.toString();
}
}
However, this doesn't create a cookie.
Any idea or a better way to implement what I want to achieve?
Firstly, you should not do this manually:
SecurityContextHolder.getContext().setAuthentication(userAuth)
It is better to employ special filter responsible for authentication, setting security context and clearing it after request is handled. By default Spring Security uses thread locals to store security context so if you don't remove it after client invocation, another client can be automatically logged in as someone else. Remember that server threads are often reused for different request by different clients.
Secondly, I would recommend using basic or digest authentication for your RESTful web service. Both are supported by Spring Security. More in docs http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
And finally, remember that RESTful web service should be stateless.
Also remember that Spring Security documentation is your friend. :-)