How can I implement access tokens for a public Spring Boot API? - spring

I have a public facing API created with Spring Boot. I want users of the API to include a token with requests so that I can ensure they are allowed to access it and also track how many requests are made within a certain period of time.
I'm a little confused on how to approach this because everything I am reading online is where a user sends their user/password and the application returns a token. This is not what I'm looking for.
I want something similar to when you use Google APIs or similar APIs where your token never changes unless you want it to change and you do not send Google API your user/pass on the first request.
Maybe I'm not using the correct terminology. Can someone point me in the right direction?

If you want to authenticate using a static token for each user you will have to create a custom AuthenticationManager that gets your request header and tries to match it aginst known keys.
Example using single key, you'd have to add a lookup to user table if needed
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
http.authorizeRequests(auth -> auth
.anyRequest().authenticated());
http.addFilter(tokenFilter());
http.csrf().disable();
return http.build();
}
public Filter tokenFilter()
{
AuthenticationTokenFilter filter = new AuthenticationTokenFilter(authenticationTokenHeader);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
protected AuthenticationManager authenticationManager()
{
final AuthenticationManager authenticationManager = new AuthenticationManager()
{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
String principal = (String) authentication.getPrincipal();
if (!authenticationTokenValue.equals(principal))
{
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
}
};
return authenticationManager;
}
Do keep in mind that this approach is not the most secure, and if you'r application is public facing I would not recommend using this. And would recommend either using Authorization header with username/password or JWT
As a side note, I think you'r mistaken on how the google API authenticates. To the best of my knowledge all google APIs use Oauth2 for authentication, the static key/file you have does not provide access to the API it is only good for retrieving a short lived access token, in essence this would be not much different from standard JWT where you use some form of credentials to get a token with which you access the API.
for more info on JWT authentication:
https://www.baeldung.com/spring-security-oauth-jwt
https://blog.softtek.com/en/token-based-api-authentication-with-spring-and-jwt

Related

Can I use both introspection server and local check for authorize token? Spring Boot - Security

I want to
introspect JWT token on remote server
and then check locally if scope/aud/iss/exp are correct
How can this be done most easily in Spring Boot?
As I understand first case is something similar to opauqeToken functionality (but I have normal JWT) and second case is more like using jwt
Spring Security only supports JWTs or Opaque Tokens, not both at the same time.
If I use opaqueToken, then validation on remote server is done without any effort (even if that's JWT)
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/api/**").hasAuthority("SCOPE_" + scope)
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)
));
return http.build();
I have scope verified. Now I want to check iss, aud, exp. Is that doable with opaqueToken?
Or should I use jwt auth instead?
IMHO opaqueToken can be JWT, so now the question is how to verify and inspect it locally after remote introspection?
It's kind of hybrid of two different approaches, but hopefully you know the simple way how to do it.
Ok, I think I have my answer. I created my own introspector which is implementing OpaqueTokenIntrospector
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector(
"introspect-url",
"client-id",
"client-secret"
);
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal introspected = this.delegate.introspect(token);
// SOME LOGIC
}
}
and I added it as a #Bean
#Bean
public OpaqueTokenIntrospector tokenIntrospector() {
return new JwtOpaqueTokenIntrospector();
}

Spring-security - httponlycookie into existing jwt intergration?

I have been told it is insecure to just use JWT without HttpOnly cookie when using a seperate frontend-service.
As suggested here:
http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/
HttpOnly Cookie: https://www.ictshore.com/ict-basics/httponly-cookie/
I currently have a working JWT system so i'm trying to upgrade this to support the cookie implementation.
I firstly changed my SecurityConfiguration to the following:
private final UserDetailsService uds;
private final PasswordEncoder bcpe;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().logout().deleteCookies(CustomAuthorizationFilter.COOKIE_NAME)
.and().authorizeRequests().antMatchers("/login/**", "/User/refreshToken", "/User/add").permitAll()
.and().authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT")
.anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
From here I am trying to insert the actual cookie implementation into my CustomAuthorizationFilter:
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login") || request.getServletPath().equals("/User/refreshToken/**")){ // DO NOTHING IF LOGGING IN OR REFRESHING TOKEN
filterChain.doFilter(request,response);
}
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length());
//NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); });
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
What I don't know is where to insert the cookie reading & where to wrap it. Does it wrap around the JWT?
I did see this implementation:
public class CookieAuthenticationFilter extends OncePerRequestFilter {
public static final String COOKIE_NAME = "auth_by_cookie";
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
Optional<Cookie> cookieAuth = Stream.of(Optional.ofNullable(httpServletRequest.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> COOKIE_NAME.equals(cookie.getName()))
.findFirst();
if (cookieAuth.isPresent()) {
SecurityContextHolder.getContext().setAuthentication(
new PreAuthenticatedAuthenticationToken(cookieAuth.get().getValue(), null));
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
Though this mentions its the "authenticationFilter", I do have an authentication filter though it is less comparable to this CookieAuthenticationFilter than CustomAuthorizationFilter:
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authManager;
public CustomAuthenticationFilter authManagerFilter;
private UserService userService;
#Override // THIS OVERRIDES THE DEFAULT SPRING SECURITY IMPLEMENTATION
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
return authManager.authenticate(authToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
// SPRING SECURITY BUILT IN USER
User springUserDetails = (User) authentication.getPrincipal();
// NEEDS SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // THIS IS USING AUTH0 DEPENDENCY
String access_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(springUserDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 120 * 60 * 1000)) // this should be 2 hours
.withIssuer(request.getRequestURI().toString())
.withClaim("roles", springUserDetails.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
...
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
}
Any suggestions are welcome!
By looking at all your custom code I would strongly recommend that you actually read the spring security documentation of the different authentication types that are available and look up the advantages and disadvantages.
And understand that there are security standards for how logins should be built and what you have built is insecure, non-scalable custom made which is very bad practice.
but here is a short recap:
FormLogin
The user authenticates themselves presenting a username and a password. In return, they will get a session cookie that contains a random string but is mapped to a key-value store on the server side.
The cookie is set to httpOnly and httpSecure which means it's harder to steal them and it's not vulnerable to XSS in the browser.
I just want to emphasize the cookie contains a random string, so if you want user information you either return the cookie after login and userinfo in the body or you do an additional call to a user endpoint and fetch user information.
The downside is that this solution does not scale if you want 5 backend servers you need something like Spring Session and set up a store, that stores the session so that it is shared between the backend servers.
Upside, we can just server-side invalidate the cookie whenever we want. We have full control.
oauth2
Well, this is the one most people know about, you want to login, and you are redirected, to an issuer (a different server). You authenticate with that server, the server gives you a temporary token that you can exchange for an opague token.
What is in opague token, well it's just a random text string that the issuer keeps track of.
Now when you want to call your backend you setup your backend as a resource server, that you present the token for in a header. The resource server extracts the token from the header, asks the issuer if the token is valid, and it answers yes or no.
Here you can revoke tokens, by going to the issuer and saying "this token is not valid anymore" and next time the token is presented it will check with the issuer that it is blocked and we are fine.
oauth2 + JWT
Like above but instead of having an opague token, we instead send a JWT to the client. So that, when the JWT has presented the resource server, does not have to ask the issuer if the token is valid. We can instead check the signature using a JWK. With this approach, we have one less call to the issuer to check the validity of the token.
JWT is just a format of a token. Opague token = random string, JWT = signed data in the format of JSON and used as a token.
JWTs were never meant to replace cookies, people just started using them instead of cookies.
But what we loose is the ability to know to revoke tokens. As we don't keep track of the JWTs in the issuer and we don't ask the issuer on each call.
We can reduce the risk here by having tokens that are short-lived. Maybe 5 minutes. But remember ITS STILL A RISK, for 5 mins malicious actors can do damage.
Your solution
If we look at your custom solution, which many people on the internet are building which has many many flaws is that you have built a FormLogin solution that gives out JWTs and hence comes with all the problems of JWTs.
So your token can be stolen in the browser as it does not have the security that comes with cookies. We have no ability to revoke tokens if it gets stolen. It is not scalable and it is custom written which means one bug and the entire application's data is compromised.
So basically all the bad things from the solutions above are combined here into one super bad solution.
My suggestion
You remove all your custom code and look at what type of application you have.
If it is a single server small application, use FormLogin, don't use JWTs at all. Cookies have worked for 20 years and they are still fine. Don't use JWTs just because want to use JWTs.
If you are writing a larger application, use a dedicated authorization server like okta, curity, spring authorization server, keycloak.
Then setup your servers to be resource servers using the built-in resource server functionality that comes with spring security and is documented in the JWT chapter in the docs.
JWTs were from the beginning never meant to be exposed to clients, since you can read everything in them they were meant to be used between servers to minimize calls to issuers because the data is signed so each server could check the signature by themselves.
Then the entire javascript community and lazy developers started writing custom insecure solutions to give out JWTs to the client.
Now everyone just googles a tutorial of spring security with JWT and builds something custom and insecure and then ask on stack overflow when "someone has pointed out that their solution is insecure".
If you are serious about building a secure login read the following:
Spring security official documentation, chapters formlogin, oauth2, JWT
The oauth2 specification
The JWT specification
Curity has good documentation about oauth2 https://curity.io/resources/learn/code-flow/
FormLogin spring
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
oauth2 spring
https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html
configure your application to handle JWTs
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
Some pointers about your code
this is completely unneeded. You have overridden a function and then you are calling the default implementation, also think about your languages in your comments.
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO FUCKING IDEA WHAT THIS DOES
return super.authenticationManagerBean();
}
Also, this entire class can be removed
public class CustomAuthorizationFilter extends OncePerRequestFilter
If you want to handle JWTs and make your server a resource server all you do is as the documentation states https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-sansboot
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
// This line sets up your server to use the built in filter
// and accept JWT tokens in headers
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
you can then setup a JWTDecoder using the built-in Nimbuslibrary that comes with spring security
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri)
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build();
}
Since it's a Bean it will automatically get injected, so we don't have to manually set anything.
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Here you have stated that you want the server to be stateless which means you have disabled cookies as cookies are what the server uses to retain the state from the clients. And then you are trying to implement a custom cookie filter.
Once again, you have to decide, are you going to use FormLogin with cookies, or oauth2 + JWT because now you are doing a mish-mash between.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
Is most likely not needed as I assume that both uds and bcpe are beans, components, etc., and will automatically get injected. No need to make something a bean THEN manually set it. You make something a bean so you DON'T have to set it manually. But you are doing both.

REST API with both apiKey and username/password authentication

I'm a Spring's beginner and I'm trying to build a REST API, connected to a React frontend in order to learn these technologies.
In order to secure this API, I added an apiKey mechanism with Spring Security, by creating a filter that checks a specific header key (API-KEY in this case), and that only allows requests that match the correct api key value.
I added this filter in my security config, which extends WebSecurityConfigurerAdapter. However, I'd like to add another authentication mechanism just to authenticate my users, in a traditional username/password way. I'm a bit lost, I read a lot of articles but all of these are using the same mechanism (filter + configure the security component). But I really don't know how to gather these two mechanisms.
I would like all requests are intercepted to check the API-KEY value, but I also would like to have an anonymous and authenticated parts in my app.
How could I achieve this ? I found some elements like interceptors but it seems to be only available for spring-mvc app.
Here's the filter I'm using :
public class ApiKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
/**
* The request header we want to check with our apiKey
*/
private String principalRequestHeader;
public ApiKeyAuthFilter(String principalRequestHeader) {
this.principalRequestHeader = principalRequestHeader;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
}
And here's my security config :
#Configuration
#EnableWebSecurity
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* The header corresponding to our apiKey
*/
#Value("${application.security.requestKey}")
private String apiKeyHeader;
/**
* The api key value we want to test with the header value
*/
#Value("${application.security.apiKey}")
private String apiKeyValue;
Logger logger = LoggerFactory.getLogger(ApiSecurityConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
ApiKeyAuthFilter filter = new ApiKeyAuthFilter(this.apiKeyHeader);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String principal = (String) authentication.getPrincipal();
if (!apiKeyValue.equals(principal)) {
throw new BadCredentialsException("The API key was not found or doesn't match the correct value");
}
logger.info("Connexion autorisée");
authentication.setAuthenticated(true);
return authentication;
}
});
http.cors().and().
antMatcher("/api/**").
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
addFilter(filter).
authorizeRequests().anyRequest().authenticated();
}
}
Do you have any clue to setup this kind of authentication ? I saw that we could define an order in our filter with methods like addFilterAfter() or addFilterBefore(), but I don't know how to setup this with my usecase.
I also found this post : How to config multiple level authentication for spring boot RESTful web service?
which seems to have the same requirements, I tried the solution provided but the authentication isn't dynamic (it's only using a string "valid-user" for its authentication filter, and I need to authenticate through my User entity stored in an in-memory h2 database. How to achieve this ?
Thank's a lot for your answers and have a nice day !

Spring Security OAuth 2.0 - client secret always required for authorization code grant

According to the spec, requests for a token using the authorization code grant are not required to be authenticated as long as the client_id is included in the request and the client_id is the same one used to generate the code. However, with the Spring Security OAuth 2.0 implementation, it appears that basic auth is always required on the /oauth/token endpoint even if the client was never assigned a secret.
It looks like there is support for allowing clients without a secret due to the isSecretRequired() method in the ClientDetails interface. What do I need to do to enable clients without a secret to be authenticated at the /oauth/token URL?
4.1.3. Access Token Request
The client makes a request to the token endpoint by sending the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:
grant_type
REQUIRED. Value MUST be set to "authorization_code".
code
REQUIRED. The authorization code received from the
authorization server.
redirect_uri
REQUIRED, if the "redirect_uri" parameter was included in the
authorization request as described in Section 4.1.1, and their
values MUST be identical.
client_id
REQUIRED, if the client is not authenticating with the
authorization server as described in Section 3.2.1.
If the client type is confidential or the client was issued client
credentials (or assigned other authentication requirements), the
client MUST authenticate with the authorization server as described
in Section 3.2.1.
Authenticating the client using the form parameters instead of basic auth is enabled using the allowFormAuthenticationForClients() method as shown in the code sample below.
class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
#Override
void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
}
}
The allowFormAuthenticationForClients() method triggers the addition of the ClientCredentialsTokenEndpointFilter which allows for authentication via form parameters.
Spring allows you to define OAuth2 clients with an empty secret.
These can be considered "public" clients, or clients that are unable to keep a secret. Think of Javascript apps, mobile apps, ..... You typically don't want to have client secrets stored there.
As you point out, according to the OAuth2 spec, a token endpoint can opt to not require a secret for these public clients.
So in Spring, simply define an OAuth2 client with an empty secret, and configure it for a limited set of grant types (authorization_code and refresh_token)
The Spring Security implementation token url will accept a token exchange without a client secret for that particular OAuth2 client.
In order to solve the issue see the method loadUserByUsername of class: org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService:
if (clientSecret == null || clientSecret.trim().length() == 0) {
clientSecret = this.emptyPassword;
}
Probably in your case emptyPassword has not been initialized with empty encoded password by your password encoder.
Set missing password encoder in AuthorizationServerConfigurerAdapter:
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder);
}
This worked for me
#Override
public void configure(AuthorizationServerSecurityConfigurer cfg) throws Exception {
cfg
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients()
.passwordEncoder(clientPasswordEncoder());
}
#Bean("clientPasswordEncoder")
PasswordEncoder clientPasswordEncoder() {
return new BCryptPasswordEncoder(4);
}
Test 1:
Test 2:
Initially I had a similar setup to the accepted answer, which is definitely a prerequisite to make this work. But what is missing is that you cannot simply set the password to null. You must set it to an empty password, for example like this:
String secret = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("");
clientDetails.setClientSecret(secret);
If you don't do this, you will still get a 401!
I use Spring boot 2.5.0 and I do not need the allowFormAuthenticationForClients(). My PasswordEncoder looks like this:
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return BCrypt.hashpw(charSequence.toString(), BCrypt.gensalt());
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return BCrypt.checkpw(charSequence.toString(), s);
}
};
}
I generated the secret as empty string through this website
just click on the button "Bcrypt" and take the hash which you will insert into a database.
like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client").secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("")) //empty
.authorizedGrantTypes("authorization_code", "refresh_token")
.redirectUris("http://www.dev.com")
.scopes("all")
.autoApprove(true);
} #Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
It is ok.

Authentication and authorization in REST Services with Liferay

We are building some services that will be exposed through a RESTful API. Primary customers of this API are Liferay portlets using Angular JS, meaning there are direct calls from client-side (Angular) to our services.
So far we have designed an authentication and authorization mechanism to assure that we can identify which logged user (Liferay) is requesting our API.
PS.: note that although we are using Liferay, it could be any other Java based application instead.
What we have designed is:
When the user logs in in our portal, Liferay creates an authentication token with userLogin (or ID) + client IP + timestamp. This token is saved in a cookie;
Before every REST call, Angular reads this cookie and sends its contents via a HTTP header;
Our service "decrypts" the cookie content sent and verifies if the timestamp is valid, the IP is the same and, according to our business rules, if the user has access to do or read whatever he wants to.
This design looks consistent to us right now and, depending on the algorithm we choose to create this token, we believe it is a secure approach.
Our doubts are:
Are we, somehow, reinventing the wheel not using HTTP authentication with some kind of custom provider? How to?
Could Spring Security help us with that? We have read some articles about it but it's not clear if it makes sense to use it with a non-Spring application;
Are there any security flaws we have not considered with this approach?
Thank you in advance. Any help is appreciated.
Filipe
Spring security solves the problem description, and as a bonus you will get all the spring security features for free.
The Token approach is great and here is how you can secure your APIs with spring-security
Implements AuthenticationEntryPoint and have the commence method set 401 instead of re-direction 3XX as follows
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
Have a TokenProcessingFilter extend and leverage what UsernamePasswordAuthenticationFilter has to offer, override the doFilter() method, extract the the token from the request headers, validate and Authenticate the token as follows
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
String authToken = this.extractAuthTokenFromRequest(httpRequest);
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
Your Spring-security configuration will look like
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthFailure authFailure;
#Autowired
private AuthSuccess authSuccess;
#Autowired
private EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationTokenProcessingFilter authTokenProcessingFilter;
#Autowired
public void configureAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Restful hence stateless
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler) // Notice the entry point
.and()
.addFilter(authTokenProcessingFilter) // Notice the filter
.authorizeRequests()
.antMatchers("/resources/**", "/api/authenticate").permitAll()
//.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/providers/**").hasRole("ADMIN")
.antMatchers("/persons").authenticated();
}
}
-- Last you will need another end point for Authentication and token-generation
Here is a spring MVC example
#Controller
#RequestMapping(value="/api")
public class TokenGenerator{
#Autowired
#Lazy
private AuthenticationManager authenticationManager;
#Autowired
private UtilityBean utilityBean;
#Autowired
private UserDetailsService userDetailsService;
#RequestMapping(value="/authenticate", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> generateToken(#RequestBody EmefanaUser user){
ResponseEntity<?> response = null;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserId(),user.getCredential());
try {
Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
/*
* Reload user as password of authentication principal will be null
* after authorization and password is needed for token generation
*/
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUserId());
String token = TokenUtils.createToken(userDetails);
response = ResponseEntity.ok(new TokenResource(utilityBean.encodePropertyValue(token)));
} catch (AuthenticationException e) {
response = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return response;
}
}
1 Generate token, 2. subsequent API-calls should have the token
Yes spring-security can do this and you don`t have to break new grounds in Authentication, Authorization.
Hope this helps
I'm late to the party but here are my two cents.
Disclaimer: The previous answers are a possible way to tackle this.
The next insight is what I've learned while implementing RESTful APIs
in Liferay.
If I understand correctly the question then you have two scenarios here. The first one is you need to create a RESTful api that will be called by already Logged in users. This means that the AJAX calls will, probably, get execute within the client's renderization of the portal. The main issue here is the security, how to secure yous REST calls.
First of all I think one should try to leverage on whatever framework one is using before implementing something else. Liferay DOES uses Spring in the backend but they've already implemented security. I would recommend to use the Delegate Servlet.
This servlet will execute any custom class and put it inside Liferay's Authentication path, meaning that you could just use PortalUtil.getUser(request) and if it's 0 or null then the user is not authenticated.
In order to use the delegate servlet you just need to configure it in your web.xml file
<servlet>
<servlet-name>My Servlet</servlet-name>
<servlet-class>com.liferay.portal.kernel.servlet.PortalDelegateServlet</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>com.samples.MyClass</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>api</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
As you can see we are instantiating another servlet. This servlet is going to be defined by the PortalDelegateServlet. The Delegate Servlet will use whatever class is on the value of the sevlet-class param. Within that class you can just check if there's a valid username in the HttpServletRequest object with Liferay's Utils and if there is then the user is OK to go.
Now, the way you access this is that the Delegate Servlet uses the value of the sub-context to know which class are you refering to from the URL. So, in this example you'll be access com.samples.MyClass by going to https://my.portal/delegate/api The 'delegate' part will always be there, the second part of the URL is what we define in the init-param. Notice that you can only define one level of the URI for sub-context, i.e. you can't set /api/v2.0/ as sub-context.
From then on you can do whatever you want on your servlet class and handle the parsing of the REST URI as you want.
You can also use spring's Dispatcher class as the class that the Delegate Servlet will call and just setup a spring servlet, hence having url annotation mappins.
It is important to know that this is only good for RESTful or Resource
serving, since the Delegate Servlet will not know how to handle
renderization of views.
The second scenario you have is to be able to call this RESTful API from any external application (doesn't matter what implementation they have). This is an entire different beast and I would have to reference the answer by iamiddy and using Spring's Authentication Token could be a nice way to do this.
Another way to do this, would be to handle unauthorized users in your servlet class by sending them to the login page or something of the sort. Once they succesfully login Liferay's Utils should recognize the authenticated user with the request. If you want to do this within an external application then you would need to mock a form-based login and just use the same cookie jar for the entire time. Although I haven't tried this, in theory it should work. Then again, in theory, communism works.
Hope this help some other poor soul out there.
Take a look at Single Sign On and Spring Security OAuth2 token authentication.
Here is example: sso-with-oauth2-angular-js-and-spring-security.
Note that Spring 4.2 might have some handy CORS support.
I can't uprate someone's answer with my current rating but The answer above is probably the right direction.
It sounds like what you need to investigate is something named CORS which provides security with cross site scripting. I'm sorry I don't quite know how it works yet (I'm in the same situation) but this is the main topic of this NSA document on REST
For Spring, try here to start maybe?

Resources