Currently we have Rest app with Spring Boot, Spring Session (Redis) and Spring Security. We use basic auth. Now I would like to add websocket support.
When client logs in successfully to other part of application, he gets x-auth-token. It is passed later as header, and it works.
Then websocket client sends this token as query parameter to connect to service.
We would like to make Spring Security and Spring Session accept token through query param in websocket connection. Is there any parameter that I can set? Or do we need to write custom authentication provider. If so, how do we integrate with Spring Session?
You can use queryString parameter for this, but I had the same issue, and my suggestion is to pass your token with headers at subscription time, or while sending messages from your JS client.
As you have a custom filter to handle your X-Auth-Token, Spring security doesn't support defining a custom-filter at websocket interceptors defined in Security.xml .
All you need is to implement interceptor for inbound channel , and to handle token authentication through access of nativeHeader sent with messages at Presend method, through an injected instance of your token authentication service.
Example for basic authentication using token servlet http request header before websocket connection:
****ws://localhost:8081/remoteservice/id?access_token=tokenValue****
verify your token return true if valid else return false
endpoint configuration:
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer{
#Autowired
RemoteServiceHandler rsHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
registry.addHandler(rsHandler, "/remoteservice/{vin}").setAllowedOrigins("*").addInterceptors(new HttpHandshakeInterceptor());
}
}
validate the token before established websocket connectin:
public class HttpHandshakeInterceptor implements HandshakeInterceptor{
#Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception
{
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String token = servletRequest.getServletRequest().getHeader("access_token");
try {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
if (claims!=null) {
return true;
}
} catch (Exception e) {
return false;
}
return false;
}
skip the http security endpoint
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().anyRequest();
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
add the request header in js file as you like
var request = URLRequest(url: URL(string: "ws://localhost:8081/remoteservice")!)
request.timeoutInterval = 5 // Sets the timeout for the connection
request.setValue("someother protocols", forHTTPHeaderField: "Sec-WebSocket-Protocol")
request.setValue("14", forHTTPHeaderField: "Sec-WebSocket-Version")
request.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol")
request.setValue("Everything is Awesome!", forHTTPHeaderField: "My-Awesome-Header")
let socket = WebSocket(request: request)
Related
I'm trying to build an Identity Provider using Spring authorization-server that third party applications are going to use for FIM (federated identity management).
We want each OAuth client to require authentication (if a user tries to login with a different client they would need to authenticate for each client).
Out of the box the flow looks like this:
So there's 2 issues.
The /oauth2/authorize endpoint just checks whether or not the sessions principal is authenticated, it doesn't care or know which client the principal was meant for.
There's just a single /login endpoint, so during authentication it doesn't know which client is used.
My best bet here is that I should:
Make the oauth2/authorize endpoint redirection to /login include the query parameter client_id
Create a custom AuthenticationFilter that also adds the client_id to the User principal
Override the authorizationRequestConverter for the oauth2/authorize endpoint and validate that the client in the request is the same as the client stored on the authenticated principal
Am I missing anything or do anyone know of a simpler way of doing this?
Based on your last comment, it seems one possibility is to simply require authentication every time, or at least every time an authorization is requested. In that case, you could clear out the authentication after the authorization code is issued to the client, using a Filter. This doesn't seem ideal and will result in a poor user experience, but may achieve your requirement.
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// ...
// Add filter to remove the SecurityContext after successful authorization
http.addFilterAfter(new RemoveSecurityContextOnAuthorizationFilter(), LogoutFilter.class);
return http.build();
}
private static final class RemoveSecurityContextOnAuthorizationFilter extends OncePerRequestFilter {
private SecurityContextHolderStrategy securityContextHolderStrategy =
SecurityContextHolder.getContextHolderStrategy();
private final LogoutHandler logoutHandler = new CompositeLogoutHandler(
new CookieClearingLogoutHandler("JSESSIONID"),
new SecurityContextLogoutHandler()
);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} finally {
String locationHeader = response.getHeader(HttpHeaders.LOCATION);
if (locationHeader != null) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
if (uriComponents.getQueryParams().containsKey("code")) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
this.logoutHandler.logout(request, response, authentication);
}
}
}
}
}
// ...
}
Problem: My java springboot application receives JWT token from external system to authenticate a user with their external identity management provider which returns the user details upon success.
Once userdetails is received, the backend application must create a redirect url for the external system end user. The redirect url will land the user on my angular application to show the landing page.
Here on, all the rest api's should be allowed through an http session.
In case the user tries to access the rest api's directly, he should get an Authentication error.
How can we achieve authorization in this case since authentication was not done by my spring boot application. Can we create custom Spring session using spring security and manually put userDetails in the SecurityContext?
I am currently dealing JWT tokens obtained from Google. Including Google, pretty much all authorization servers provide rest APIs such as GET /userInfo, where you can carry the JWT token in the request header or in the URL as a GET parameter, and then verify if the JWT token is valid, non-expired, etc.
Because verifying a JWT token is usually stateless, these APIs generally come with a generous limit and you can call them as many times as you need.
I assume that you have Spring security integrated and then you can add a filter. In this way, every request has to be verified for its token in the header.
#Service
public class TokenAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String header = request.getHeader("Authorization");
RestTemplate restTemplate = new RestTemplate(); // If you use Google SDK, xxx SDK, you do not have to use restTemplate
String userInfoUrl = "https://example.com/api/userInfo";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", header);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<String> response = restTemplate.exchange(
userInfoUrl, HttpMethod.GET, entity, String.class, param);
User user = response.getBody(); // Get your response and figure out if the Token is valid.
// If the token is valid? Check it here....
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.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);
}
}
I have been really been searching high and low for the last few days on how to do this and have finally decided to admit defeat and ask for help, please!!!
I have followed Dr Dave Syer's tutorial on Angular and Spring Security specifically the Zuul Proxy as an api gateway and using Spring Session with Redis (https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/double#_sso_with_oauth2_angular_js_and_spring_security_part_v)
The issue I am having is that I am calling resource rest services via the gateway from an external application with the following header:
String plainCreds = "user:password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
to be authenticated and then routed by zuul and then the resource to have access to the authenticated session via redis.
The issue is that the session seems to only commit to redis in the gateway after the request has responded. So what is happening is that when I call a resource service with the header, I can see the successful authentication occurring in the gateway and session being created, however I am getting a 403 in the resource due to the session not being in redis after its been routed via zuul.
However if I get the error, grab the session id and add it to the header and try again it works because now my authenticated session is available for the resource project after its been routed.
Please could someone point me in the direction of how I go about getting my calls via the gateway to authenticate and route in the same request please?
Thanks
Justin
I followed Justin Taylor's posts on different pages so this is his solution. It makes me sense to have solution with source code here:
Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property #EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
Simple Zuul filter for adding session into current request's header:
#Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
}
Once more - this is not my solution - credentials go to Justin Taylor.
I am so sorry about the delayed response here, one of the great things about South Africa is our great telecoms hehe, I have had no internet at home for a while and my source code for this is on my home pc.
Yes Steve is on the right track. There are two issues that you need to be resolve here:
Spring session only commits the authenticated session to redis on response to the initial incoming request. So the first step is to follow that link steve provided to ensure spring session commits to redis whenever the session changes.
Zuul doesn't propagate this newly authenticated session on the initial routing. So what you need to do is to use a zuul pre filter (lots of examples around) that gets the authenticated session id and then adds it to the zuul request to the resource behind the gateway. You will see a setter method on the zuul request to set the session id.
If you don't do this, you will need to do two calls, one to authenticate and get a valid session id which would be in redis from spring session, and then the subsequent call with your authenticated session id.
I did battle with this for a while, but when I got it working it was spot on. I extended this solution to not only work for http basic, but added in a jwt token implementation.
Hopefully this helps, as soon as I am connected at home I can post the source.
Good Luck!
Justin
My APIGateway (Zuul) is proxied by Apache Httpd and protected by Mellon module (SAML 2.0). After a successfully authentication on the identity provider, mellon module inject correctly some headers read into the SAML response, but the first request fails with a 403 status code.
I'm also using SpringSecurity, to solve the problem I'm using a simple filter added on the security filter chain that ensure the correct creation of SecurityContext:
#Component
public class MellonFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(MellonFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String mellonId=req.getHeader("mellon-nameid");
if(mellonId==null||mellonId.isEmpty())
;//do filterchain
else {
UserWithRoles userWithRoles = new UserWithRoles();
userWithRoles.setUsername(mellonId);
SilUserDetails details = new SilUserDetails(userWithRoles);
SilAuthenticationPrincipal silPrincipal = null;
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("Some roles");
silPrincipal = new SilAuthenticationPrincipal(details, true, authorities);
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(silPrincipal);
}
filterChain.doFilter(req,httpServletResponse);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
if(SecurityContextHolder.getContext().getAuthentication()!=null&&SecurityContextHolder.getContext().getAuthentication() instanceof SilAuthenticationPrincipal)
return true;
return false;
}
}
Then I need a ZuulFilter to save the session (on Redis) and to propagate the actual session id:
public class ZuulSessionCookieFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(ZuulSessionCookieFilter.class);
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
httpSession.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext()
);
Session session = repository.findById(httpSession.getId());
context.addZuulRequestHeader("cookie", "SESSION=" + base64Encode(httpSession.getId()));
log.debug("ZuulPreFilter session proxy: {} and {}", session.getId(),httpSession.getId());
return null;
}
private static String base64Encode(String value) {
byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
return new String(encodedCookieBytes);
}
}
I hope this solution will be helpful to everyone.
Introduction, Requirements:
right now i am writing a Single Page Application with AngularJS which talks to a Spring REST API. For security purposes I would like to setup a reverse proxy with zuul which proxies every request to the API and verifies that the user is authenticated. Also, if the user is not authenticated he should be redirected to an OpenAM instance (functioning as OAuth 2 Authorization Server). If the user is authenticated the request should be forwarded to the API with a Json Web Token (JWT) in the Header, containing at least the LDAP groups of the User.
In short I would like to have something like a API Gateway similar to the solution in this tutorial: https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v
Status quo
I setup the Spring Cloud Security and Zuul with the following config:
server:
port: 9000
spring:
oauth2:
sso:
home:
secure: false
path: /,/**/*.html
client:
accessTokenUri: http://openam.example.org:8080/OpenAMTest/oauth2/access_token
userAuthorizationUri: http://openam.example.org:8080/OpenAMTest/oauth2/authorize
clientId: bearer-client
clientSecret: clientsecret
scope: openid profile
resource:
userInfoUri: http://openam.example.org:8080/OpenAMTest/oauth2/userinfo
zuul:
routes:
exampleApp:
path: /example-application/**
url: http://openam.example.org:8081/example-application
The Application class looks like the following:
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Sso
public class TestZuulProxy extends SpringBootServletInitializer {
public static void main(String[] args){
SpringApplication.run(TestZuulProxy.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<TestZuulProxy> applicationClass = TestZuulProxy.class;
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html", "/home.html", "/")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
}
}
Now when i go to the "example-application" i get forwarded to the OpenAM authorization login screen. When I type in the credentials I can access the "example-application". Console log on the Gateway Service:
2015-06-22 17:14:10.911 INFO 6964 --- [nio-9000-exec-3] o.s.c.s.o.r.UserInfoTokenServices : Getting user info from: http://openam.example.org:8080/OpenAMTest/oauth2/userinfo
2015-06-22 17:14:10.953 INFO 6964 --- [nio-9000-exec-3] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Mon Jun 22 17:14:10 CEST 2015, principal=Aaccf Amar, type=AUTHENTICATION_SUCCESS, data={details=remoteAddress=0:0:0:0:0:0:0:1, sessionId=<SESSION>, tokenType=BearertokenValue=<TOKEN>}]
Http-Header read by Zuul Filter:
authorization --- Bearer c2b75b5a-c026-4e07-b8b9-81e9162c9277
x-forwarded-host --- localhost:9000
x-forwarded-prefix --- /example-application
So something works! I have an access-token that gets forwarded to the REST-API.
Problem
1) This solution does not really meet my requirements, because I don't want the REST API to call the token-endpoint of OpenAM. I want that a JWT with the nessessary claims gets passed to the API in the Header. Should I create a JWT in the Gateway (e.g. Zuul Filter) manually or is there another solution?
2) In the solution above, when the access-token expires Zuul keeps forwarding me to the API. Why is this? Doesn't Spring Oauth2 checks if the access-token expires? how can I implement that?
3) I also tried to configure the tokenInfoUri in application.yml, but then I am getting a "405 Method Not Allowed" exception, because I think OpenAM expects a GET request on the tokeninfo-Endpoint. Can I customize this somehow? Which Classes do I need to override/customize to change the request.
If you have an advices, ideas or possible solutions, let me know!
Thank you!
If you want to use a JWT in your application, configure OpenAM as an OpenID Connect provider (OpenAM 12.0 or later). Once the user has authenticated OpenAM will issue a JWT with a number of claims about the user. Your SPA can pass that along in requests to your service tier.
If you want a gateway to enforce AuthN/ AuthZ on the users session, you can use something like ForgeRock's OpenIG. This can act as a policy enforcement point, and has the ability to introspect JWT tokens.
I have questions on the following areas: spring-session and spring-security.
Spring Session
I have a application protected with Spring Security through basic in-memory authentication as provided in the example sample.
I see spring is creating session id's even the authentication is not successful, meaning I am seeing x-auth-token in my response header as well in the Redis DB even if I don't supply basic authentication credential details.
How do we avoid creating sessions for authentication failures?
Spring Security
Want to use spring security to protect resources assuming spring session creates session only for the protected resources.
Assuming a Signin API (/signin - HTTP Post) validates (username & password) credentials against a third-party REST API .
Once the external API validates the credentials, how do I update the spring security context on the successful authentication?
Access to other secured resources with the x-auth-token needs to be validated and based on the information access to the secured resource should be provided.
Do we need to have Spring Security in this case or shall I use a basic filter and spring session? What is recommended?
Typically it would be best to break your questions into multiple StackOverflow questions since you are more likely to find someone that knows the answer to a single question than both.
How do we avoid creating sessions for authentication failures ?
By default Spring Security will save the last unauthenticated request to session so that after you authenticate it can automatically make the request again. For example, in a browser if you request example.com/a/b/c and are not authenticated, it will save example.com/a/b/c to the HttpSession and then have the user authenticate. After you are authenticated, it will automatically give you the result of example.com/a/b/c. This provides a nice user experience so that your users do not need to type the URL again.
In the case of a REST service this is not necessary since the client would remember which URL needs to be re-requested. You can prevent the saving by modifying the configuration to use a NullRequestCache as shown below:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
You can provide custom authentication by providing your own AuthenticationProvider. For example:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = (String) token.getCredentials();
// validate making REST call
boolean success = true;
// likely your REST call will return the roles for the user
String[] roles = new String[] { "ROLE_USER" };
if(!success) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(roles));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
You can then configure your RestAuthenticationProvider using something like this:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
public RestAuthenticationProvider restAuthenticationProvider() {
return new RestAuthenticationProvider();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, AuthenticationProvider provider) throws Exception {
auth
.authenticationProvider(provider);
}
}
Session IDs are getting stored in Redis even when authentication fails.
Rob's answer of setting NullRequestCache didn't work for me. That is, there were redis entries even after setting request cache to NullRequestCache. To make it work, I had to use an authentication failure handler.
http.formLogin().failureHandler(authenticationFailureHandler()).permitAll();
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler();
}
public class AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
}
Note that the failure handler does nothing but extend the default handler explicitly. It returns a 401 in case of failure. If you were doing redirects, you can configure it in the handler easily.