Spring STOMP Token Based Security - java

I want to implement token based authentication for web socket connection.
My backend system is server and a machine is client, how to implement authentication during sending and receiving message between machine and server, no user authentication here.
#Configuration
#EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String authToken = accessor.getFirstNativeHeader("X-Auth-Token");
log.debug("webSocket token is {}", authToken);
Principal user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
However, I'm totally lost how I would do at "Principal user = ... ;". How would I get Principle with the token? As here no user exist, only communication between a machine and a backend server

Guess you can try to get principal from spring security context
SecurityContextHolder.getContext().getAuthentication().getPrincipal();

Sounds like you might need a solution similar to something that would be used by someone building an IoT solution. A good starting point would be to have a look at OpenID Connect.
There's a good article here discussing the related challenges and solutions:
https://www.gluu.org/blog/oauth2-for-iot/
OpenId Connect's site:
http://openid.net/connect/

I know this issue has been around for a long time, and there are already plenty of answers available on Stack Overflow, such as this and this. These answers indeed fix this issue but all are trying to have their own implementations of the Principle interface which is not needed as Spring Security has already implemented all we need.
Answer your question: The principle needs to be created from the token provided in the request. You can easily convert string token to a Principle by using new BearerTokenAuthenticationToken("token-string-without-bearer"), then after that, you should authenticate the token by using authenticationManager.authenticate(bearerTokenAuthenticationToken). Therefore, the code is like this:
// access authentication header(s)
Principal user = authenticationManager.authenticate(new BearerTokenAuthenticationToken(authToken));
The full implementation of the configureClientInboundChannel method should be (sorry I only have Kotlin code, but you should be able to get the idea):
override fun configureClientInboundChannel(registration: ChannelRegistration) {
registration.interceptors(object : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
val accessor = MessageHeaderAccessor
.getAccessor(message, StompHeaderAccessor::class.java)
if (accessor != null && accessor.command == StompCommand.CONNECT) {
// assume the value of Authorization header has Bearer at the beginning
val authorization = accessor.getFirstNativeHeader("Authorization")
?.split(" ", limit = 2)
?: return message
// check token type is Bearer
if (authorization.getOrNull(0) == "Bearer") {
val token = authorization.getOrNull(1)?.takeIf { it.isNotBlank() }
// if token exists, authenticate and (if succeeded) then assign user to the session
if (token != null) {
val user = try {
authenticationManager.authenticate(BearerTokenAuthenticationToken(token))
} catch (ex: AuthenticationException) {
// if throw an exception, do not touch the user header
null
}
if (user != null) {
accessor.user = user
}
}
}
}
return message
}
})
}
Note that BearerTokenAuthenticationToken comes from org.springframework.boot:spring-boot-starter-oauth2-resource-server, so you need this dependency in order to use it.
authenticationManager comes from the implementation of WebSecurityConfigurerAdapter, exposing as a Bean by overriding authenticationManagerBean method and mark it as #Bean, and then injecting the Bean into your AbstractWebSocketMessageBrokerConfigurer implementation, which in this case, is MyConfig.
Also, don't forget to mark MyConfig class with #Order(Ordered.HIGHEST_PRECEDENCE + 99)
so that it sets up the user header before Spring Security takeover.

Related

How to manage Scopes according to the User and not the Client?

I've been following this tutorial in order to create an Authentication Server, but I'm facing some problems regarding the concepts, I guess.
Look, when I register a Client in Repository, I have to define some parameters, like its id, secret, authentication method, grant types, redirection uris and scopes:
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
When I'm back to my Resource Server, I find that my client was successfully logged in and it returns with an "articles.read" scope. Everything is fine here, supposing that I want to protect my endpoints with the Client's scope, but this is not my case.
In my situation, I want to protect my endpoints according to my User's role in database.
I'll give you an example, so you don't have to read the whole Baeldung's website:
I try to access: http://localhost:8080/articles.
It redirects to: http://auth-server:9000, where a Spring Security Login Form appears.
When you submit the proper credentials (which are compared from a database using the default Spring Security schema), it basically gets you back to: http://localhost:8080/articles.
Well, in that point, I have an Authorization Token with the Client scope, but not the logged User role.
Is there an standard way to configure my project to achieve this or, do I have to think of a creative way to do so?
Thank you in advance.
For role based authentication you should map authorities in Oauth token.
OAuth2AuthenticationToken.getAuthorities() is used for authorizing requests, such as in hasRole('USER') or hasRole('ADMIN').
For this you need to implement the userAuthoritiesMapper, something like this:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}

How can I bypass the Oauth2 authentication with predefined tokens in Spring Boot?

In Spring Boot project, I want to bypass authentication for some "magic" tokens. For example, I get a request header parameter as Authorization:Bearer abcdef-xyz. If this is not a valid access token, I'll check my predefined tokens. If one of them is matched, I will create a dummy user for the Security Context, and allow the request to proceed.
Since you are aware of the security risk, see if the following solution works out.
request header parameter as Authorization:Bearer abcdef-xyz
This will not be supported out-of-the-box by Spring-boot and if you want this solution, you will have to write custom implementation and that could be complex.
Rather, you can look at a solution where you create a Long expiring token with the low level authorization privileges that you want and ask your clients to send this token
To achieve this, set the accessTokenValiditySeconds to Zero or negative to make the token into non-expiring tokens
I've found a way to implement this.
Override readAccessToken method on InMemoryTokenStore
Check the token value according to your needs
If the token fits your pattern, get related info from token and validate
If it is validated then you can create a dummy authentication and call storeAccessToken method
public OAuth2AccessToken readAccessToken(String tokenValue) {
Pattern pattern = Pattern.compile(HRN_REGEX);
Matcher matcher = pattern.matcher(tokenValue);
OAuth2AccessToken oAuth2AccessToken = super.readAccessToken(tokenValue);
if (oAuth2AccessToken == null && matcher.find()) {
String group = matcher.group();
String companyStr = group.replace(LICENCE_TOKEN_PREFIX, "");
Long companyId= Long.valueOf(companyStr);
boolean isCompanyValid = mLoginUserService.validateCompanyIdForAnonymousLogin(companyId);
if (isCompanyValid) {
OAuth2Request storedReq = new OAuth2Request(generateDummyReqParameters(), CLIENT, null, true, generateScope(), generateResourceIds(), null, null, null);
Authentication userAuth = new AnonymousAuthenticationToken(companyStr, companyId, AuthorityUtils.createAuthorityList(ROLE_COMPANY));
OAuth2Authentication authentication = new OAuth2Authentication(storedReq, userAuth);
storeAccessToken(new DefaultOAuth2AccessToken(tokenValue), authentication);
return new DefaultOAuth2AccessToken(tokenValue);
} else {
throw new InvalidTokenException(COMPANY_IS_NOT_LICENCED);
}
}
return oAuth2AccessToken;}
Give a role to this authentication ("ROLE_COMPANY" in my case)
In your ResourceServerConfigurerAdapter class override configure(HttpSecurity http) method and give this role to related endpoints
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/tools/validate/**").hasAnyAuthority(ROLE_COMPANY, ROLE_USER, ROLE_ADMIN)
.anyRequest().hasAnyAuthority(ROLE_USER, ROLE_ADMIN); }

Enabling OAuth2 with Feign for scheduled cross-service tasks

I'm trying to solve a puzzle with enabling OAuth2-based authentication for my Feign client that is used for cross-service communication.
In normal cases, when a user pushes a request through API, I'm able to take all authentication details needed from the Spring's SecurityContextHolder (as it normally does its job and fills all the details objects) and enhance Feign's Request as follows:
public class FeignAccessTokenRelyInterceptor implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(FeignAccessTokenRelyInterceptor.class);
#Override
public void apply(RequestTemplate template) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
String tokenValue = null;
if (auth.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
tokenValue = details.getTokenValue();
}
if (tokenValue == null) {
log.warn("Current token value is null");
return;
}
template.header("Authorization", "Bearer " + tokenValue);
}
}
}
However, when it comes to scheduled calls that are triggered inside the system, the SecurityContext is obviously empty. I'm filling it with UserInfoTokenServices by manually requesting the access token by client credentials flow beforehand and loading user details:
OAuth2Authentication authentication = userInfoTokenServices.loadAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
But such construction doesn't fill OAuth2Authentication.details, on which I rely to get an access token. I tried extending OAuth2AuthenticationDetails, but the only constructor requires HttpServletRequest which is hard to get inside a scheduled task, and making a dummy instance of it feels like a bad choice.
So far, I see the only adequate option to make a separate custom implementation of details holder and pass it to OAuth2Authentication along with the access token I have. And then pick it up in FeignAccessTokenRelyInterceptor.
The question
Maybe there are some other options where I can store my access token in the security context and reliably get it from there, in order not to produce new custom classes?
Will be glad for any help.
Some related links I've studied:
How to get custom user info from OAuth2 authorization server /user endpoint
Spring Boot / Spring Cloud / Spring Security: How to correctly obtain an OAuth2 Access Token in a scheduled task
Spring #FeignClient , OAuth2 and #Scheduled not working
How can I authenticate a system user for scheduled processes in Spring?
For the history, hope that'd help someone like me struggling with that.
I didn't find a better way than the initial one and made a custom InternalOAuth2Details to hold a token value obtained from Spring's OAuth services. Then, in the FeignAccessTokenRelyInterceptor I simply check if current details are InternalOAuth2Details and try to get a token value from it, as follows:
#Override
public void apply(RequestTemplate template) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
String tokenValue = null;
if (auth.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
tokenValue = details.getTokenValue();
} else if (auth.getDetails() instanceof InternalOAuth2Details) {
InternalOAuth2Details details = (InternalOAuth2Details) auth.getDetails();
tokenValue = details.getTokenValue();
}
if (tokenValue == null) {
log.warn("Current token value is null");
return;
}
template.header("Authorization", "Bearer " + tokenValue);
}
}
I bet it isn't the best solution, but it seems to work quite stable as of now.

How to check that user has already logged in using Apache Shiro?

The question is very simple. I'd like to restrict user access with same login from different machines/browsers: only one live user session is possible.
Apache shiro library is used for user authentification and managment.
Of course this could be done using simple synchornized maps and etc. But the question is: Has Apache Shiro special mechanisms for that or not?
Another variant of this question: how to reveice the list of all subjects who are logged in the system using apache shiro?
UPD:
To clarify my question. My desire is to have some code like this (I known, that there isn't such class exception, but the idea must be more clean):
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(login, password);
try {
currentUser.login(token);
} catch (AlreadyAuthenticatedException aae) {
errorMsg = "You should logoff on another machine!";
}
The Shiro sessions are stored in SessionDAO with sessionId as keys. Without extra effort you cannot access a session by a principal (user name). However, you could extend DefaultSecurityManager and check all active sessions by SessionDAO.getActiveSessions.
The following codes could be a simple example (suppose you are not using WebSubject):
public class UniquePrincipalSecurityManager extends org.apache.shiro.mgt.DefaultSecurityManager {
#Override
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
String loginPrincipal = (String) token.getPrincipal();
DefaultSessionManager sm = (DefaultSessionManager) getSessionManager();
for (Session session : sm.getSessionDAO().getActiveSessions()) {
SimplePrincipalCollection p = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (p != null && loginPrincipal.equals(p.getPrimaryPrincipal())) {
throw new AlreadyAuthenticatedException();
}
}
return super.login(subject, token);
}
}

JAX-RS: How to secure REST endpoints?

I am using JBoss AS and JAX-RS for creating REST endpoints.
Lets say my class looks like
#Path("/users")
public class UserResource {
#GET
public Response getAccount() {
return "hello";
}
}
Now getAccount is not authenticated at the moment
Wanted
- I would like to add authentication so that when code hits getAccount the user is authenticated
- I would like the authentication to be driven by annotations instead of XML configurations, if at all possible
- I would like to do the database comparison to see if the user is valid
Problem
- I have never done that so I have no idea how to implement it
- I have googled around a lot and found Jersey examples
UPDATE
- I would like to send authentication credentials with each request and not creating any session
Please guide me with one simple working example and I would try to extend from there
You need is a Stateless Spring Security configuration in front of your JAX RS end points.
I have addressed exact problem you are trying to solve but I don't have my own code to share..
Here is one project which has done the exact thing you are asking, Some wise man has done it all for you ;)
https://github.com/philipsorst/angular-rest-springsecurity
What is the magic ?
You have one unprotected URL which does the Authentication, and set the user roles as well..
Then you return some kind of Token, put it some where in cache which will be expected on every subsequent call..
Upon new request on other protected resources, you will check if the Token is present in your cache/session store ( you need some mechanism to keep track of valid tokens )
If token is resent and valid, you do the programmatic Log-in in Spring Security which ensures that you can use all the Security features spring provides, ( Annotations, JSTL Tags etc.. ) !
Once passed token validation you will get the logged in user details in your controllers ( aka JAX RS resources ) to deal with security further..
If the token was not valid or not present , it would be trapped by failure end point which would return appropriate response ( 401 )
Refer Following Link To Understand How Stateless Spring Security is configured..,
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/resources/context.xml
See how a user is validated for the first time and a token is generated..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/resources/UserResource.java
Here is the class where programmatic login is performed on every request after token
check..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/AuthenticationTokenProcessingFilter.java
I solved this with following code.
note Token mechanism will be updated once I do that
I have solved this by modifying the interceptor I have, the following is code
Annotation
#Inherited
#InterceptorBinding
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface SecurityChecked {
}
Resource Class
public class SecureResource {
#GET
#SecurityChecked
public Response getUser() {
return Response.ok("authenticated successfully!").build();
}
}
Interceptor class
#Interceptor
#Provider
#ServerInterceptor
#SecurityChecked
public class SecurityCheckInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityCheckInterceptor.class);
#Nullable
#Override
public ServerResponse preProcess(final HttpRequest request, final ResourceMethod method) throws Failure, WebApplicationException {
final List<String> authToken = request.getHttpHeaders().getRequestHeader("X-AUTH");
if (authToken == null || !isValidToken(authToken.get(0))) {
final ServerResponse serverResponse = new ServerResponse();
serverResponse.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
return serverResponse;
}
return null;
}
private static boolean isValidToken(#Nonnull final String authToken) {
LOGGER.info("validating token: " + authToken);
return true;
}
#SuppressWarnings("rawtypes")
#Override
public boolean accept(final Class declaring, final Method method) {
// return declaring.isAnnotationPresent(SecurityChecked.class); // if annotation on class
return method.isAnnotationPresent(SecurityChecked.class);
}
}
and then I run my Integration tests by deploying the resource class in JBoss and issuing following commands on command-line
curl --header 'X-AUTH: 1a629d035831feadOOO4uFReLyEW8aTmrCS' http://localhost:8080/market-1.0-SNAPSHOT/rest/login
curl --header 'InvalidHeader: InvalidHeaderValue' http://localhost:8080/market-1.0-SNAPSHOT/rest/login

Categories

Resources