Spring oauth2login oidc grant access based on user info - spring

I'm trying to set up Authentication based on this tutorial: https://www.baeldung.com/spring-security-openid-connect part 7 specifically.
I have filled properties and configured filter chain like this:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.oauth2Login(oauthLogin -> oauthLogin.permitAll());
return http.build();
}
which works, but now all users from oidc can connect log in. I want to restrict access based on userinfo. E.g. add some logic like:
if(principal.getName() == "admin") {
//allow authentication
}
are there any way to do it?
I tried to create customer provider like suggested here: Add Custom AuthenticationProvider to Spring Boot + oauth +oidc
but it fails with exception and says that principal is null.

You can retrieve user info when authentication is successful and do further checks based user info.
Here is sample code that clears security context and redirects the request:
#Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
// OidcUser or OAuth2User
// OidcUser user = (OidcUser) token.getPrincipal();
OAuth2User user = token.getPrincipal();
if(!user.getName().equals("admin")) {
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
redirectStrategy.sendRedirect(request, response, "login or error page url");
}
}
}
}

Are you sure that what you want to secure does not include #RestController or #Controller with #ResponseBody? If so, the client configuration you are referring to is not adapted: you need to setup resource-server configuration for this endpoints.
I wrote a tutorial to write apps with two filter-chains: one for resource-server and an other one for client endpoints.
The complete set of tutorials the one linked above belongs to explains how to achieve advanced access-control on resource-server. Thanks to the userAuthoritiesMapper configured in resource-server_with_ui, you can write the same security expressions based on roles on client controller methods as I do on resource-server ones.

Related

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.

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

Spring boot basic authentication with token for a RESTAPI

I need to provide user login with SpringBoot application.
User login request will be a Rest request having payload comprise of "username" and "password".
I need to validate those credentials first time from DB and generate a token having validity for specific time.
Then after login all the subsequent requests will have that token, and that token will be verified each time.
I have done the token verification part but I am really confused about first time login, I have no clue how to do it.
Even on first time login request, system is going to check for token authentication which obviously getting failed.
I want system to simply generate token on first time after validating name and password from db.
This is the first time I am implementing User login with Spring Boot Security, so I am pretty clueless about it. Although I have researched and read a lot online but still not able to figure out this part.
EDIT:
Following is the security config class which extends WebSecurityConfigurerAdapter
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/","**/firstPage").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login").
permitAll()
.and().logout().permitAll();
}
Following is the request that will be called after login.How to authenticate user in it using the token already generated? Token is being sent in Header of the request.
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> TestReques(#RequestBody TestParam testParam)
throws Exception {
//Some logic
}
If you disable form login from spring security configuration class and expose one rest endpoint (/auth) you can handle login and generate token.Here i used jwt for token generation.
#RequestMapping(value = "/auth", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException, IOException {
// Perform the security
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword());
final Authentication authentication = authManager.authenticate(token);
if (!authentication.isAuthenticated()) {
throw new BadCredentialsException("Unknown username or password");
}
// Reload password post-security so we can generate token
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwtoken = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(responseBean);
}
When use stateless authentication we can pass token parameter explicitly to controller and validate it.In case session based authentication is on we can also use #AuthenticationPrincipal for to retrieve current logged in user.
//Stateless authentication
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> test(#RequestBody TestParam testParam,String token)
throws Exception {
Boolean isValidToken = jwtTokenUtil.validateToken(token);
if(isValidToken) {
//Some logic
}else {
//invalid request
}
}
#PostMapping(value = "/home")
public ResponseEntity<ConsolidateResponse> test(#RequestBody TestBean requestToken,
#AuthenticationPrincipal User contextPrincipal, HttpServletRequest req) {
Optional.ofNullable(contextPrincipal).orElseThrow(InvalidUserSession::new);
//some logic
}

How to redirect UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders?

Using Spring Security 4.02, can anyone help with some tips on how I can handle UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders so that authenticated requests, with the correct header, but which are unauthorized, are sent to a specific URL instead of the forms-login page?
Let me explain further what I'm trying to accomplish for accessing a web app being secured by SSO behind a proxy. Not all users who are authenticated by SSO will have access to this app. So I need to account for 3 access scenarios:
authenticated user (header is present) is authorized (username/roles are present in app's db)
authenticated user (header is present) is unauthorized (username/roles are not present in app's db)
unauthenticated user with username/roles present in app's db
The actions when accessing the website should be:
authenticated/authorized user proceeds directly to target URL
authenticated/unauthorized user is redirected to error/info page
unauthenticated user is redirected to forms-login page for authentication
With my current configuration, scenarios 1 & 3 appear to be working as desired. For scenario 2 I've tried setting RequestHeaderAuthenticationFilter#setExceptionIfHeaderMissing to both true and false.
If setExceptionIfHeaderMissing=false, authenticated/unauthorized request is handled by ExceptionTranslationFilter where AccessDeniedException is thrown and user is redirected to forms-login page.
If setExceptionIfHeaderMissing=true, authenticated/unauthorized request encounters PreAuthenticatedCredentialsNotFoundException from AbstractPreAuthenticatedProcessingFilter.doAuthenticate and HTTP 500 is returned.
So I've read and reread the Spring Security reference and api documents and scoured the web and just can't quite figure out what I need to do. I think I somehow need to enable some kind of filter or handler to trap the PreAuthenticatedCredentialsNotFoundException with a redirected response. But I can't seem to wrap my head around how to implement that with all the spring tools available. Can someone please offer some specifics? Many thanks in advance!!
Here is my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String AUTHENTICATION_HEADER_NAME = "PKE_SUBJECT";
#Autowired
CustomUserDetailsServiceImpl customUserDetailsServiceImpl;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
auth.userDetailsService(customUserDetailsServiceImpl);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and()
.authorizeRequests()
.antMatchers("/javax.faces.resource/**", "/resources/**", "/templates/**", "/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/public/welcome.xhtml")
.and()
.addFilter(requestHeaderAuthenticationFilter());
}
#Bean PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() throws Exception {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return provider;
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader(AUTHENTICATION_HEADER_NAME);
filter.setAuthenticationManager(authenticationManagerBean());
filter.setExceptionIfHeaderMissing(true);
return filter;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>
userDetailsServiceWrapper() throws Exception {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper
= new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
wrapper.setUserDetailsService(customUserDetailsServiceImpl);
return wrapper;
}
}
My customized UserDetailsService:
#Service("customUserDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetailDO userDetail = userRepo.getUserDetailById(username);
if(userDetail == null) {
throw new UsernameNotFoundException("user is not authorized for this application");
}
List<UserRoleDO> roles = userRepo.getRolesByUsername(username);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if(CollectionUtils.isNotEmpty(roles)) {
for(UserRoleDO role : roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRole());
authorities.add(authority);
}
}
UserDetails user = new User(username, "N/A", authorities);
return user;
}
}
I realized that I did not need to handle the exception. What I did was to shift my thinking on this. I realized that even if the username was not found by the customUserDetailsService, the request was still an authenticated request since the request is trusted to be authenticated by the SSO and the proxy server.
So instead of returning a UsernameNotFoundException I returned the org.springframework.security.core.userdetails.User with an empty Authorities collection. And because the RequestHeaderAuthenticationFilter.setExceptionIfHeaderMissing = false by default, no exception is thrown and then the authenticated request is passed to the access filter where it is determined that the request has no authorization to access any resources. So instead of redirecting to the next authentication filter which would be the forms login provider, a 403 Access Denied http status is returned which I can then override to redirect to a user-friendly error page.

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