Token Based Authorization for REST services using Spring Security+Spring Boot - spring

I am building REST services(JAVA/SPRING) for mobile applications in which have to take care of Authentication and Authorization. For Authentication I am using external tool but for role based Authorization I want to use Spring Security. The project uses Spring Boot +Spring Data JPA+ Spring REST.
I did a sample Project to get some hands on Spring Security but after spending few hours I was able to make it work some how, but have specific doubts.
Few classes from Sample:-
#Configuration
#EnableWebSecurity
#ComponentScan("com.ezetap.security")
#EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired DataSource dataSource;
#Autowired CustomAuthenticationProvider authenticationProvider;
#Autowired CustomerUserDetailService customerUserDetailService;//Overrides loadByUserName method
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(customerUserDetailService);
auth.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/spring-security/test/**").hasRole("USER");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();
http.addFilterBefore(new RestAuthenticationFilter(authenticationProvider,customerUserDetailService), BasicAuthenticationFilter.class);
}
}
public class RestAuthenticationFilter extends GenericFilterBean {
CustomerUserDetailService authenticationService;
AuthenticationProvider authenticationProvider;
public RestAuthenticationFilter() {
}
public RestAuthenticationFilter(CustomerUserDetailService customerUserDetailService) {
this.authenticationService=customerUserDetailService;
}
public RestAuthenticationFilter(AuthenticationProvider authenticationProvider,
CustomerUserDetailService customerUserDetailService) {
this.authenticationProvider=authenticationProvider;
this.authenticationService=customerUserDetailService;
}
public final String HEADER_SECURITY_TOKEN = "X-CustomToken";
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if("test".equalsIgnoreCase(request.getHeader(HEADER_SECURITY_TOKEN))){
UserDetails userDetails=authenticationService.loadUserByUsername("");//assume this is working
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}else{
response.sendError(401, "Authorization failed");
//response.getWriter().append("Access denied");
}
} <br>
I have to use Token based Authorization in the Header. What will be the correct way to Generate tokens and then encrypt and send to Browser which then be used with all requests.
Is their any service where Spring generates a Token and persists or I have to do it manually and generate a token and store it somewhere and then evict it periodically?
I should also be able to get all tokens or invalidate a particular token or some other token related services.
Also, please let me know if the approach I am following is correct or if some other thing needs to be considered.This is purely for REST services, so no state or session. Dont want to use Spring Oauth due to complexity and time constraints.

Related

Cookie Authentication instead of JWT Bearer Token after a successful Oauth2 Login in Spring Boot

I'm using callicoder's spring-boot-react-oauth2-social-login-demo
sample to implement a rest api using Oauth2 client. Sample works without a problem.
However after a successful Oauth2 authentication, I want to issue a cookie instead of JWT Token to secure access to my controllers. In order to do this, I added the lines below determineTargetUrl on OAuth2AuthenticationSuccessHandler. This sets a cookie containing JWT token created by TokenProvider.
CookieUtils.addCookie(response, appProperties.getAuth().getAuthenticationCookieName(), token, (int) appProperties.getAuth().getTokenExpirationMsec());
And then I created a CookieAuthenticationFilter similar to TokenAuthenticationFilter which checks the cookie set by OAuth2AuthenticationSuccessHandler.
public class CookieAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private AppProperties appProperties;
#Autowired
private TokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
Optional<String> jwt = CookieUtils.getCookie(request, appProperties.getAuth().getAuthenticationCookieName()).map(Cookie::getValue);
if (StringUtils.hasText(String.valueOf(jwt)) && tokenProvider.validateToken(String.valueOf(jwt))) {
Long userId = tokenProvider.getUserIdFromToken(String.valueOf(jwt));
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
}
and on SecurityConfig I replaced tokenAuthenticationFilter bean to cookieAuthenticationFilter
http.addFilterBefore(cookieAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
When I run the project, Oauth2 authentication is made successfully and cookie is set. However when I request a secured controller method, CookieAuthenticationFilter.doFilterInternal is not hit and request directly goes to RestAuthenticationEntryPoint.commence and exception is thrown with message Full authentication is required to access this resource .
Do I have to change any more configuration to change authentication to cookie from Bearer (JWT)?
The problem was a result of a missing exception without catch. The sample and the code works as expected.

Is possible ask for an acces token oauth2 just with refresh token in spring security? without basic authentication?

I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...

Required a bean of type 'org.springframework.security.authentication.AuthenticationManager' that could not be found. message from spring security

I am trying to implement one sample demo for Spring Security with Spring Boot for checking the authentication. I am trying to implement a basic workout for Spring Security and getting the following message,
Description:
Parameter 0 of constructor in com.spacestudy.service.CustomAuthenticationProvider required a bean of type 'org.springframework.security.authentication.AuthenticationManager' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.web.AuthenticationEntryPoint' in your configuration.
My security config class SecurityConfig.java,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationEntryPoint authEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new ApplicationContextHeaderFilter((ApplicationContext) authenticationManager()));
}
}
And my BasicAuthenticationFilter implementation like the following,
#Component
public class CustomAuthenticationProvider extends BasicAuthenticationFilter {
public CustomAuthenticationProvider(AuthenticationManager authenticationManager) {
super(authenticationManager);
// TODO Auto-generated constructor stub
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String bearerToken = request.getHeader("accessToken");
String username = "test";
String password = "test";
if (username != null && !username.isEmpty()) {
return new UsernamePasswordAuthenticationToken(username, null, null);
}
return null;
}
}
How can I resolve this issue?
Lot of problems are there in your code.
(ApplicationContext) authenticationManager()
you can not cast AuthenticationManager to ApplicationContext
.addFilter(new ApplicationContextHeaderFilter(...))
I don't know Why you are using ApplicationContextHeaderFilter for simple demo application.
You should have preferred BasicAuthenticationFilter or even simple default configuration provided for HttpSecurity with .httpBasic()
You should have preferred UsernamePasswordAuthenticationFilter or even simple default configuration provided in HttpSecurity with .formLogin()
CustomAuthenticationProvider extends BasicAuthenticationFilter
An authentication provider is one which implements AuthenticationProvider interface. In your case naming should be xxxAuthFilter.
You have done nothing in below code.(got existing authentication object and set it back without creating an valid authentication object.)
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
Coming to the AuthenticationManager and AuthFilters implementation point of view,
For add filter you can add any implementation of Spring Security provided filters as given below
.addFilter(AnyFilterImplementationFromThisLink)
(But not all filters are auth filters. Where auth filters will attempt to authenticate with the authenticationManager configured)
For example If you consider UsernamePasswordAuthenticationFilter or BasicAuthenticationFilter
you should take care of setting AuthenticationManager where your auth manager should override authenticate() method and it should return Authentication object(where Authentication object will have auth principal, credentials and granted authorities list)
Or
If you don't want to implement authentication manager...
In simple way in your filters(implementation of OncePerRequestFilter) doFilterInternal() method set the `Authentication` object in `SecurityContext`
List<GrantedAuthority> authorityList = new ArrayList<>();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
authorityList.add(authority);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, authorityList);
//Note UsernamePasswordAuthenticationToken implements Authentication
SecurityContextHolder.getContext().setAuthentication(authToken);
How any auth filter works is if there is a valid Authentication object then filter chain will continue without attempting authentication otherwise it will attemptAuthentication by overrided attemptAuthentication() method.
But your ApplicationContextHeaderFilter is a implementation of OncePerRequestFilter where it has no attemptAuthentication() and i don't know the order of ApplicationContextHeaderFilter if it's order is after creating security context then you can set the authentication object to security context.
Your error seems to be that the AuthenticationManager is not present as a Spring Bean.
Option 1
Register an AuthenticationManager in Spring Bean. All is provided by Spring for do this directly in your SecurityConfig class by overriding the WebSecurityConfigurerAdapter#authenticationManagerBean method like explain in the documentation of it
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Option 2
Avoid to register an AuthenticationManager in Spring, but directly your CustomAuthenticationProvider classs.
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public CustomAuthenticationProvider authenticationProvider() throws Exception {
return new CustomAuthenticationProvider(authenticationManager());
}
}
Don't forget to remove the #Component annotation on the CustomAuthenticationProvider class with this method.
I am not sure but shoudn't the CustomAuthenticationProvider implement AuthenticationProvider and AuthenticationManager is just a container for authentication providers and it seems that you dont have any.
Check this site for more info
https://www.baeldung.com/spring-security-authentication-provider
You can try on this, put it in config security file
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
Create a new java class. And Configure like below:
#Configuration
#RequiredArgsConstructor
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
}

spring boot restful service disable Chrome login popup

I am building a restful web service with an Angular front end. If the users log in with wrong credentials, the Chrome browser automatically shows a login popup. I want to prevent this by responding to code 403 instead of 401.
i have review [this question][1] and it does not help with my use case. I have also tried different solutions found on google, but in all cases is stays giving 401 with the login popup.
With the newest Spring versions, the configuration to handle authentication has changed. For this reason most tutorials do not work anymore. here is the working configuration:
SecurityConfig.java:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//....
//.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and().csrf().disable();
}
}
CustomAuthenticationAntryPoint.java:
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.debug("Pre-authenticated entry point called. Rejecting access");
httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad credentials");
}
}

How to get/set the principal and session attributes from Spring 4 stomp websocket methods

I'm doing experiments with Spring 4 websockets and stomp, and I have a hard time figuring out how to get/set the current user and other session attributes in a message handling method annotated with #MessageMapping.
The documentation says that the message handling methods can take a Principal as argument, and I found that the principal is retrieved by Spring by calling getUserPrincipal() on the native socket session, and then associated with the socket session, but I haven't found any way to easily customize this behavior, other than writing a servlet filter and wrap the original request into a wrapper returning the principal found in my cookie.
So my questions are:
How to manually set the principal to the socket session, when the client connects (I have this information thanks to a custom cookie, and I don't use Spring security)?
If 1 is not possible, how to add additional attributes to the socket session when the client connects?
How to access the socket session and its attributes from a message handling method?
Is there a way to access the login and passcode sent by the browser at connection time. They seem to be completely ignore by Spring and not accessible.
UPDATE: With Spring 4.1 it is possible to set the user on the handshake for #1 from above. Per the Spring documentation you can create a new class which extends DefaultHandshakeHandler and override the determineUser method. Additionally you can also create a security filter which sets the principal as well if you have a token. I have implemented the second one myself and I include some sample code for both below.
For #2 and #3 I do not think that it is possible still. For #4 Spring intentionally ignores these per the documentation here.
SAMPLE CODE FOR DefaultHandshakeHandler SUBCLASS:
#Configuration
#EnableWebSocketMessageBroker
public class ApplicationWebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
public class MyHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// add your own code to determine the user
return null;
}
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/myEndPoint").setHandshakeHandler(new MyHandshakeHandler());
}
}
SAMPLE CODE FOR SECURITY FILTER:
public class ApplicationSecurityTokenFilter extends GenericFilterBean {
private final static String AUTHENTICATION_PARAMETER = "authentication";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
// check to see if already authenticated before trying again
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if ((existingAuth == null) || !existingAuth.isAuthenticated()) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
UsernamePasswordAuthenticationToken token = extractToken(request);
// dump token into security context (for authentication-provider to pick up)
if (token != null) { // if it exists
SecurityContextHolder.getContext().setAuthentication(token);
}
}
}
filterChain.doFilter(servletRequest,servletResponse);
}
private UsernamePasswordAuthenticationToken extractToken( HttpServletRequest request ) {
UsernamePasswordAuthenticationToken authenticationToken = null;
// do what you need to extract the information for a token
// in this example we assume a query string that has an authenticate
// parameter with a "user:password" string. A new UsernamePasswordAuthenticationToken
// is created and then normal authentication happens using this info.
// This is just a sample and I am sure there are more secure ways to do this.
if (request.getQueryString() != null) {
String[] pairs = request.getQueryString().split("&");
for (String pair : pairs) {
String[] pairTokens = pair.split("=");
if (pairTokens.length == 2) {
if (AUTHENTICATION_PARAMETER.equals(pairTokens[0])) {
String[] tokens = pairTokens[1].split(":");
if (tokens.length == 2) {
log.debug("Using credentials: " + pairTokens[1]);
authenticationToken = new UsernamePasswordAuthenticationToken(tokens[0], tokens[1]);
}
}
}
}
}
return authenticationToken;
}
}
// set up your web security for the area in question
#Configuration
public class SubscriptionWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/myEndPoint**","/myEndPoint/**").and()
.addFilterBefore(new ApplicationSecurityTokenFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic() // leave this if you want non web browser clients to connect and add an auth header
.and()
.csrf().disable();
}
}
** NOTE: ** DO NOT declare your filter as a Bean. If you do then it will also be picked up (at least using Spring Boot) in the generic filters so it will fire on every request.
This is impossible for the time being (Spring 4.0). An issue has been opened (and considered) at Spring: https://jira.springsource.org/browse/SPR-11228

Resources