Vaadin 14 and Push: Accessing Spring SecurityContext and Authentication outside the request thread - spring

I had the same problem this guy had:
https://vaadin.com/forum/thread/3383122/17008971
When receiving a status from the server I try to refresh content via push (using ui.access) on the client. That content needs the current principal's information.
final SecurityContext securityContext = SecurityContextHolder.getContext();
final Authentication authentication = securityContext.getAuthentication();
this
authentication
is returning null.
He solved this problem using Vaadin Shared Security, but I can't find any repo or library called Vaadin Shared Sec. Are there any other ways to solve the problem?

Why not just get the auth details in the view constructor and make them class-scoped, or have a bean to do that and set its values ? E.g.
#Route("some-view")
public class SomeView extends VerticalLayout {
Authentication authentication;
private void doHeavyStuff() {
try {
Thread.sleep(1500);
} catch (InterruptedException ex) {
// ignore
}
}
public SomeView() {
authentication = SecurityContextHolder.getContext().getAuthentication();
final Button button = new Button("Click me", e -> {
Notification.show("CLICKED");
getUI().ifPresent(ui -> {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
doHeavyStuff();
ui.access(() -> {
Notification.show("Calculation done");
});
});
});
});
add(button);
// simple link to the logout endpoint provided by Spring Security
Element logoutLink = ElementFactory.createAnchor("logout", "Logout");
getElement().appendChild(logoutLink);
}
}
I based this answer (tested it as well) in this tutorial, if you want to learn more about it:
https://vaadin.com/learn/tutorials/securing-your-app-with-spring-security/speciale

Related

Start a session from a given id in spring

How can I create a session in a spring mvc application from a given ID instead of a generated one?
I want to fixate the session.
The fixation will be started by a trusted ui service. This trusted service forwards all requests. Thus the fixation can't begin in browser. It is not intended to do it without this ui service.
Providing a HttpSessionIdResolver bean does not work, since it only changes the location in HTTP response. Eg Session ID in HTTP header after authorization.
Are there any solutions without creating a shadow session management?
Session is required for keycloak integration. Guess it's not possible to use keycloak in stateless mode.
Thanks in advance
It is possible to fixate a session with spring-session.
#Configuration
#EnableSpringHttpSession
public class SessionConfig {
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return new HttpSessionIdResolver() {
public List<String> resolveSessionIds(HttpServletRequest request) {
final var sessionId = request.getHeader("X-SessionId");
request.setAttribute(SessionConfig.class.getName() + "SessionIdAttr", sessionId);
return List.of(sessionId);
}
public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {}
public void expireSession(HttpServletRequest request, HttpServletResponse response) {}
};
}
#Bean
public SessionRepository<MapSession> sessionRepository() {
return new MapSessionRepository(new HashMap<>()) {
#Override
public MapSession createSession() {
var sessionId =
(String)RequestContextHolder
.currentRequestAttributes()
.getAttribute(SessionConfig.class.getName()+"SessionIdAttr", 0);
final var session = super.createSession();
if (sessionId != null) {
session.setId(sessionId);
}
return session;
}
};
}
In #resolveSessionIds() you can read the ID and store for later use.
createSession() is called when no session has been found and a new one is required. Here you can create the session with previously remembered ID in MapSession#setId(String).
Even if is possible, IMO it is not a good idea. There might be other architectural solutions/problems.

How to create custom claims in JWT using spring-authorization-server

I'm building an OAuth2 authorization server based on the experimental Spring project Spring Authorization Server
My use case is quite simple, fetch users from a DB, and based on some properties of the user, set some custom claims in the JWT being produced.
I haven't found a way to do so with Spring Authorization Server, the only way I could work out is to inject a jwtCustomizer object as part of the JwtEncoder bean definition:
#Bean
public JwtEncoder jwtEncoder(CryptoKeySource keySource) {
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(keySource);
jwtEncoder.setJwtCustomizer((headersBuilder, claimsBuilder) -> {
// Inject some headers and claims...
});
return jwtEncoder;
}
This obviously doesn't give me access to users information, therefore I can't set the claims I need at this point.
Did anyone manage to solve this problem?
The solution for this is in a test of the library
#Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim(AUTHORITIES_CLAIM, authorities);
}
};
}
You can try following way. Though it is Kotlin code, not Java, but approach should be clear:
import org.springframework.security.oauth2.provider.token.TokenEnhancer
class UserTokenEnhancer : TokenEnhancer {
override fun enhance(accessToken: OAuth2AccessToken,
authentication: OAuth2Authentication): OAuth2AccessToken {
val username = authentication.userAuthentication.name
val additionalInfo = mapOf( /* populate with some data for given username */ )
(accessToken as DefaultOAuth2AccessToken).additionalInformation = additionalInfo
return accessToken
}
}
Then just register bean:
#Bean
fun userTokenEnhancer(): TokenEnhancer {
return UserTokenEnhancer()
}

How to implement Session Tracking in spring MVC?

I am very new to spring mvc, I have to develop a web application based on session tracking and my application is annotation based. In my web app I have route each page based on the username and role existence in session. Initially I have been using HttpSession as parameter to controller method, but it is very difficult to check each and every request. I know there are many application level security ways in spring, but I really couldn't understand how to use them. Please suggest me some solutions, For all help thanks in advance.
After updating with interceptors:
Controller class
// Method to showLogin page to user
#RequestMapping(value = "user")
public ModelAndView showLoginToUser(#ModelAttribute("VMFE") VmFeUser VMFE,HttpSession session) {
System.out.println("#C====>showLoginToUser()===> ");
ModelAndView view = new ModelAndView();
//session.setAttribute("user_name", "no_user");
try {
view.setViewName("login");
} catch (Exception e) {
e.printStackTrace();
}
return view;
}
Interceptor
public class HelloWorldInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle (HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
RequestMapping rm = ((HandlerMethod) handler).getMethodAnnotation(
RequestMapping.class);
boolean alreadyLoggedIn = request.getSession()
.getAttribute("user_name") != null;
boolean loginPageRequested = rm != null && rm.value().length > 0
&& "login".equals(rm.value()[0]);
if (alreadyLoggedIn && loginPageRequested) {
//response.sendRedirect(request.getContextPath() + "/app/main-age");
return false;
} else if (!alreadyLoggedIn && !loginPageRequested) {
System.out.println("REDIRECTING===");
response.sendRedirect(request.getContextPath() + "/user");
return false;
}
return true;
}
}
Using spring security you can implement session tracking and apply filters to validate requests. Spring security is very easy to implement. Kindly follow spring security tutorial click here.
You can also check my git repo for implementation click here. It's a angular spring boot application and i have used spring security and JWT for authentication and authorization.
Hope it helps you thanks.

Calling session from jQuery Mobile client with Spring Security authentication and JAX-RS resource

My JAX-RS resource for authentication looks like this (by the way I want to improve it for security matters because it's not safe to call the username and password by a GET Method):
#Path("/UserService")
public class UserServiceRS {
UserService user ;
AuthenticationManager authManager;
public UserServiceRS(){
user=(UserService)SpringApplicationContext.getBean("userDetailsService");
authManager(AuthenticationManager)SpringApplicationContext
.getBean("authenticationManager");
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/authentification/{username}/{password}")
public String login(#PathParam( value="username" ) String username,
#PathParam( value="password" ) String password )
{
Logger LOG = LoggerFactory.getLogger(LoginBean.class);
LOG.info("Starting to login");
try{
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username,password);
Authentication authenticate = authManager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authenticate);
return "1";
}catch (final Exception e){
LOG.error("Error log in" + e);
}
return "0";
}
And in my jQuery Mobile client the method I call in the "connect" button looks like this:
function login(){
var uri="myuri";
var login=$("#login").val();
var password=$("#pass").val();
uri=uri+"/"+login+"/"+password;
$.getJSON(uri,function(data){
if(JSON.stringify(data)==1){
$.mobile.changePage("#page-signup-succeeded",{transition:"slide" });
}
else {
$.mobile.changePage("#page-signup-failed");
}
});
};
I would like to manage the session in my jQuery Mobile client and I don't know how to get the session from the authenticated user. How can I achieve this?

Spring Session Redis and Spring Security how to update user session?

I am building a spring REST web application using spring boot, spring secuirity, and spring session (redis). I am building a cloud application following the gateway pattern using spring cloud and zuul proxy. Within this pattern I am using spring session to manage the HttpSesssion in redis and using that to authorize requests on my resource servers. When an operation is executed that alters the session's authorities, I would like to update that object so that the user does not have to log out to have the updates reflected. Does anyone have a solution for this?
To update the authorities you need to modify the authentication object in two places. One in the Security Context and the other in the Request Context. Your principal object will be org.springframework.security.core.userdetails.User or extend that class (if you have overridden UserDetailsService). This works for modifying the current user.
Authentication newAuth = new UsernamePasswordAuthenticationToken({YourPrincipalObject},null,List<? extends GrantedAuthority>)
SecurityContextHolder.getContext().setAuthentication(newAuth);
RequestContextHolder.currentRequestAttributes().setAttribute("SPRING_SECURITY_CONTEXT", newAuth, RequestAttributes.SCOPE_GLOBAL_SESSION);
To update the session using spring session for any logged in user requires a custom filter. The filter stores a set of sessions that have been modified by some process. A messaging system updates that value when new sessions need to be modified. When a request has a matching session key, the filter looks up the user in the database to fetch the updates. Then it updates the "SPRING_SECURITY_CONTEXT" property on the session and updates the Authentication in the SecurityContextHolder. The user does not need to log out. When specifying the order of your filter it is important that it comes after SpringSessionRepositoryFilter. That object has an #Order of -2147483598 so I just altered my filter by one to make sure it is the next one that is executed.
The workflow looks like:
Modify User A Authority
Send Message To Filter
Add User A Session Keys to Set (In the filter)
Next time User A passed through the filter, update their session
#Component
#Order(UpdateAuthFilter.ORDER_AFTER_SPRING_SESSION)
public class UpdateAuthFilter extends OncePerRequestFilter
{
public static final int ORDER_AFTER_SPRING_SESSION = -2147483597;
private Logger log = LoggerFactory.getLogger(this.getClass());
private Set<String> permissionsToUpdate = new HashSet<>();
#Autowired
private UserJPARepository userJPARepository;
private void modifySessionSet(String sessionKey, boolean add)
{
if (add) {
permissionsToUpdate.add(sessionKey);
} else {
permissionsToUpdate.remove(sessionKey);
}
}
public void addUserSessionsToSet(UpdateUserSessionMessage updateUserSessionMessage)
{
log.info("UPDATE_USER_SESSION - {} - received", updateUserSessionMessage.getUuid().toString());
updateUserSessionMessage.getSessionKeys().forEach(sessionKey -> modifySessionSet(sessionKey, true));
//clear keys for sessions not in redis
log.info("UPDATE_USER_SESSION - {} - success", updateUserSessionMessage.getUuid().toString());
}
#Override
public void destroy()
{
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException
{
HttpSession session = httpServletRequest.getSession();
if (session != null)
{
String sessionId = session.getId();
if (permissionsToUpdate.contains(sessionId))
{
try
{
SecurityContextImpl securityContextImpl = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
if (securityContextImpl != null)
{
Authentication auth = securityContextImpl.getAuthentication();
Optional<User> user = auth != null
? userJPARepository.findByUsername(auth.getName())
: Optional.empty();
if (user.isPresent())
{
user.get().getAccessControls().forEach(ac -> ac.setUsers(null));
MyCustomUser myCustomUser = new MyCustomUser (user.get().getUsername(),
user.get().getPassword(),
user.get().getAccessControls(),
user.get().getOrganization().getId());
final Authentication newAuth = new UsernamePasswordAuthenticationToken(myCustomUser ,
null,
user.get().getAccessControls());
SecurityContextHolder.getContext().setAuthentication(newAuth);
session.setAttribute("SPRING_SECURITY_CONTEXT", newAuth);
}
else
{
//invalidate the session if the user could not be found
session.invalidate();
}
}
else
{
//invalidate the session if the user could not be found
session.invalidate();
}
}
finally
{
modifySessionSet(sessionId, false);
}
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}

Resources