I have a Windows service "A" being used for authentication purposes (NOT managed by us) and I have Spring-boot based REST Api service "B" (managed by us) which uses Zuul to route traffic. There is an external service "C" (NOT managed by us) that needs to talk to the Windows service through our REST Apis. Since "A" uses NTLM authentication we need to pass the request body from "C" and add the ntlm credentials in the headers at "B" and route the traffic using zuul.
My question is, how do I add NTLM credentials in Java to the routed traffic in zuul headers?
~ Jatin
You need to write your own ZuulFilter.
Something along the lines of
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.ZuulFilter;
public class MyFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// now add your headers to the request
return null;
}
}
In your app just make sure the filter bean is created and it will be automatically registered:
#EnableZuulProxy
#SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
}
Have a look at this guide for more info.
Zuul will work fine with Spring Session. There are many blogs about this.
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html
Related
We have a Spring Boot microservice that does the SOAP call to the external system using org.springframework.ws.client.core.WebServiceTemplate.
Now the system would be protected with Keycloak, so all the request need to beak the auth token.
If it was a REST API, I would just replace the pre-existed RestTemplate with OAuth2RestTemplate. But how to instrument the calls initially done by the org.springframework.ws.client.core.WebServiceTemplate ?
So, I understand, I should put the authentication header manually with value 'Bearer ....token there...'. How I can retrieve that part manually to put it into the request?
you can get current request token using RequestContextHolder class and add into soap request header.
String token = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest().getHeader("Authorization");
Also I would suggest use Webservice interecptor instead of adding header in each web service request call.
The problem was caused by
Existing library code, based on org.springframework.ws.client.core.WebServiceTemplate, so large and huge for rewriting it using WebClient, compatible with OAuth2 SpringSecurity or use depricated OAuth2RestTemplate
The webservice we previously communicated with, turns into protected with Gravitee and accepts queries with JWT tokens only. So, the only change here is to add the Authentication header with 'Bearer ....token there...'
We initiate the call from the scheduled jo in the microservice. So, it should be getting token from the Keycloak before the request and be able to update it with time. No one does the explicit authorization like in the frontend, so the OAuth2 client should use client-id and client-secret to connect with no human involved
The Solution
At the beginning, we define the Interceptor to the SOAP calls, that will pass the token as a header, via a Supplier function taking it wherever it can be taken:
public class JwtClientInterceptor implements ClientInterceptor {
private final Supplier<String> jwtToken;
public JwtClientInterceptor(Supplier<String> jwtToken) {
this.jwtToken = jwtToken;
}
#Override
public boolean handleRequest(MessageContext messageContext) {
SoapMessage soapMessage = (SoapMessage) messageContext. getRequest();
SoapHeader soapHeader = soapMessage.getSoapHeader();
soapHeader.addHeaderElement(new QName("authorization"))
.setText(String. format("Bearer %s", jwtToken.get()));
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
}
}
Then pass it to the template in addition to other pre-existed interceptor to be called in config class:
protected WebServiceTemplate buildWebServiceTemplate(Jaxb2Marshaller marshaller,
HttpComponentsMessageSender messageSender, String uri, Supplier<String> jwtToken) {
WebServiceTemplate template = new WebServiceTemplate();
template.setMarshaller(marshaller);
template.setUnmarshaller(marshaller);
template.setMessageSender(messageSender);
template.setDefaultUri(uri);
ClientInterceptor[] clientInterceptors = ArrayUtils.addAll(template.getInterceptors(), new Logger(), new JwtClientInterceptor(jwtToken));
template.setInterceptors(clientInterceptors);
return template;
}
Then add the Spring Security Oath2 Client library
compile 'org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE'
We create OAuth2AuthorizedClientService bean, that uses a standard ClientRegistrationRepository (the repository is initiated through usage of #EnableWebSecurity annotation on the #Configuration class, but please double check about that)
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
Then create a OAuth2AuthorizedClientManager
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Authentication authentication = new Authentication() {
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
GrantedAuthority grantedAuthority = new GrantedAuthority() {
#Override
public String getAuthority() {
return "take_a_needed_value_from_property";
}
};
return Arrays.asList(grantedAuthority);
}
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return new Principal() {
#Override
public String getName() {
return "our_client_id_from_properties";
}
};
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return "take_a_needed_name_from_properties";
}
};
//we need to emulate Principal there, as other classes relies on it. In fact, Principal isn't needed for the app which is a client and just do the call, as nothing is authorized in the app against this Principal itself
OAuth2AuthorizationContext oAuth2AuthorizationContext = OAuth2AuthorizationContext.withClientRegistration(clientRegistrationRepository.findByRegistrationId("keycloak")).
principal(authentication).
build();
oAuth2AuthorizationContext.getPrincipal().setAuthenticated(true);
oAuth2AuthorizationContext.getAuthorizedClient();
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().
//refreshToken().
clientCredentials(). //- we use this one according to our set up
//authorizationCode().
build();
OAuth2AuthorizedClientService oAuth2AuthorizedClientService = oAuth2AuthorizedClientService(clientRegistrationRepository); //use the bean from before step here
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, oAuth2AuthorizedClientService);
OAuth2AuthorizedClient oAuth2AuthorizedClient = authorizedClientProvider.authorize(oAuth2AuthorizationContext);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
oAuth2AuthorizedClientService.saveAuthorizedClient(oAuth2AuthorizedClient,
oAuth2AuthorizationContext.getPrincipal());
//this step is needed, as without explicit authorize call, the
//oAuth2AuthorizedClient isn't initialized in the service
return authorizedClientManager;
}
Provide a method for supplied function that can be called each time to retrieve the JWT token from the security stuff (repository and manager). Here it should be auto-updated, so we just call for retrieving it
public Supplier<String> getJwtToken() {
return () -> {
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient("keycloak", "we_havePout_realm_there_from_the_properties");
return authorizedClient.getAccessToken().getTokenValue();
};
}
Pass this Consumer to the #Bean, defining the WebServiceTemplate's
#Bean
public Client client(#Qualifier("Sender1") HttpComponentsMessageSender bnfoMessageSender,
#Qualifier("Sender2") HttpComponentsMessageSender uhMessageSender) {
WebServiceTemplate sender1= buildWebServiceTemplate(buildSender1Marshaller(), sender1MessageSender, properties.getUriSender1(),getJwtToken());
WebServiceTemplate sender2 = buildWebServiceTemplate(buildSender2Marshaller(), sender2MessageSender, properties.getUriSender2(),getJwtToken());
return buildClient(buildRetryTemplate(), sender1, sender2);
}
We add Spring Security Client values to application.yaml in order to configure it.
spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: https://host/keycloak/auth/realms/ourrealm
registration:
keycloak:
client-id: client_id
client-secret: client-secret-here
authorization-grant-type: client_credentials //need to add explicitly, otherwise would try other grant-type by default and never get the token!
client-authentication-method: post //need to have this explicitly, otherwise use basic that doesn't fit best the keycloak set up
scope: openid //if your don't have it, it checks all available scopes on url like https://host/keycloak/auth/realms/ourrealm/ .well-known/openid-configuration keycloak and then sends them as value of parameter named 'scope' in the query for retrieving the token; that works wrong on our keycloak, so to replace this auto-picked value, we place the explicit scopes list here
I'm currently using Zuul and Ribbon as a reverse proxy and load balancer respectively. I'm also using Eureka as service discovery. I have more than one instance of a service in Eureka, and I want to know the hostname of the server chosen by Ribbon.
This is my current configuration:
GatewayApplication.java:
#EnableZuulProxy
#EnableDiscoveryClient
#SpringBootApplication
public class GatewayApplication {
static RequestQueue q = new RequestQueue();
public static void main(String[] args) {
q.start();
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public LogIncomingRequest logIncomingRequest() {
return new LogIncomingRequest(q);
}
#Bean
public LogLeavingRequest logLeavingRequest() {
return new LogLeavingRequest(q);
}
}
application.yml:
server:
port: 4000
spring:
application:
name: zuul-gateway
zuul:
sensitive-headers:
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA:10.0.2.15:8761}/eureka/
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
I also have a pre and a post filter. How can I get the information about the server that was chosen by ribbon?
I found this code but I have no idea where to use it and how to access the information.
#Component
public class RibbonInterceptor extends ZoneAvoidanceRule {
#Override
public Server choose(Object key) {
Server choose = super.choose(key);
System.out.println(choose);
return choose;
}
Are there any other solutions?
Thanks in advance!
Implement a "pre" filter in your zuul api gateway, if you have a look at PreDecorationFilter of zuul, you will see that it determines where and how to route based on the supplied. Also sets various proxy related headers for downstream requests
in your filters run method
context = RequestContext.getCurrentContext();
request = context.getRequest();
call the getRouteHost method on context object it will give you all the route related information like protocol, host, port etc..
RequestContext.getCurrentContext().getRouteHost();
NOTE: the order of your filter should be > 5, since preDecorationFilter has order of 5
#Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER;
}
I just managed to find a solution for my problem. I created a POST filter and i got the information i needed using this code:
RequestContext ctx = RequestContext.getCurrentContext();
((IResponse) ctx.get("ribbonResponse")).getRequestedURI();
The solution that Fábio Pina posted just works for me. Here is what I'm using for logging:
#Component
public class ZuulLoggingFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
RequestContext ctx = RequestContext.getCurrentContext();
logger.info("********************************************************");
logger.info("RequestedURI -> {}", ((IResponse) ctx.get("ribbonResponse")).getRequestedURI());
logger.info("********************************************************");
return null;
}
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return 1;
}
}
I am following this answer: Best practice for REST token-based authentication with JAX-RS and Jersey to implement the REST API authentication. But the JAX-RS Authentication Filter is not triggered via #Secured annotation.
I finished until the part Securing your REST endpoints by adding #Secured on the method.
This is how I am doing this.
Secured.java
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
AuthenticationFilter.java
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String AUTHENTICATION_SCHEME = "Bearer";
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
validateToken(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// Authentication scheme comparison must be case-insensitive
return (authorizationHeader != null &&
authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " "));
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code
// The "WWW-Authenticate" is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME)
.build());
}
private void validateToken(String token) throws Exception {
// Check if it was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
MyApis.java
#Path("api")
public class MyApis {
#GET
#Secured
#Path("me")
#Produces(MediaType.APPLICATION_JSON)
public Map<String, X500Name> whoAmI() {
return ImmutableMap.of("me", legalName);
}
// other APIs
}
When I call /api/me, I can get the response directly without any authentication header provided. It seems like the filter is not triggered or not registered properly.
I have seen this question JAX RS, my filter is not working, but it does not solve my problem.
And how I understand the following is that web.xml is not needed right?
This solution uses only the JAX-RS 2.0 API, avoiding any vendor specific solution. So, it should work with the most popular JAX-RS 2.0 implementations, such as Jersey, RESTEasy and Apache CXF.
It's important mention that if you are using a token-based authentication, you are not relying on the standard Java EE web application security mechanisms offered by the servlet container and configurable via application's web.xml descriptor.
Late for answer, but the solution to this problem is to add AuthenticationFilter to your classes.
public class App extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(MyApis.class);
classes.add(AuthenticationFilter.class);
return classes;
}
}
We have migrated from Basic Authentication to Keycloak method in our project in the production environment. However we would like continue using Basic Authentication, for local development, standalone and demo instalations, which could be triggered by a profile or something like this.
In this project we have REST APIs developed with Java/Spring boot and an AngularJS application which consumes these APIs. We are using Keycloak to protect both AngularJS app and the APIs.
The problem is how to make Spring Security and Keycloak to work "together" in the same application with different profiles. The solution I found so far, was to configure both Spring Security and Keycloak, and made a workaround with properties files, as described below:
application-keycloak.properties
#Unactivate Basic Authentication
security.ignored=/**
application-local-auth.properties
#Unactivate Keycloak
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
When I wanto to use keycloak, I have to ignore security in order to not have problems and when I want to use basic authentication I have to exclude Keycloak configuration in order to also prevent conflicts.
This is my Security Configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.authorizeRequests()
.antMatchers("/","/scripts/**","/keycloak/isActive","/keycloak/config","/bower_components/**","/views/**","/fonts/**",
"/views/inventory/dialogs/**", "/services/**","/resources/**","/styles/**", "/info")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
And this is my Keycloak Spring Boot configuration:
# Keycloak
keycloak.realm=local
keycloak.realmKey=MIIBIjANBgkqhkiG9wsIIBCgKCAQEAuJYmaWvF3YhifflJhspXOs8RJn74w+eVD8PtpVbu2cYG9OIa49P8SwqVn/kyJQr7kT3OlCq3XMZWBHe+JSzSz7KttKkhfFSfzISdKDKlkPena2H/i3FKlRZIldbeeuQNYdD6nMpzU6QWLwGF1cUAo1M11f2p99QI1FOhVPJSErWsjDsKpWqG+rMMjT1eos0QCNP7krx/yfMdlUyaJCYiDvpOAoec3OWXvDJovEajBNAZMWVXgJF90wAVPRF6szraA2m7K2gG9ozaCNWB0v4Sy6czekbKjqEBPJo45uEmGHd92V//uf/WQG4HSiuv8CTV+b6TQxKtZCpQpqp2DyCLewIDAQAB
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=none
keycloak.resource=App-backend
keycloak.bearer-only=true
keycloak.credentials.secret=a714aede-5af9-4560-8c9d-d655c831772f
keycloak.securityConstraints[0].securityCollections[0].name=Secured API
keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=ROLE_USER
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/*
It is working, however I think it is not an elegant solution. I have tried to implement this using the Keycloak property enable-basic-auth, but I could not understand how it works but it seems that it is just to protect Rest APIs, it does not allow the browser to create a session and use it for all the other requests.
Have someone ever had to implement something like this and can give me some better idea?
I managed to solve this. However, how beautiful my solution is is up for debate.
My use case is that I need to secure most of my endpoints using Keycloak but some (for batch processing) should just use Basic Auth. Configuring both has the downside that Keycloak tries to validate the Authorization Header even if it is Basic Auth so I needed to do three things.
Deactivate all automatic security for my batch route.
Write a custom request filter which secures the batch route.
Manipulate the servlet request object such that the zealous keycloak filter doesn't trip on it.
My security configuration.
#EnableWebSecurity
#EnableResourceServer
public class SecurityConfiguration extends KeycloakWebSecurityConfigureAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
// usual configuration ...
.antMatchers("/api/v1/batch/**").permitAll() // decouple security for this route
.anyRequest().denyAll();
}
}
My custom request filter (needs to run before the spring security filter, thus the ordering annotation):
#Component
#Slf4j
#Order(Ordered.HIGHEST_PRECEDENCE + 2)
public class BasicAuthRequestFilter extends OncePerRequestFilter {
#Value("${batch.user}")
private String user;
#Value("${batch.password}")
private String password;
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
if (isBatchRequest(request)) {
SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
if (AuthOutcome.AUTHENTICATED.equals(auth(facade))) {
filterChain.doFilter(new AuthentifiedHttpServletRequest(request), response);
}
log.debug("Basic auth failed");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
return;
}
filterChain.doFilter(request, response);
}
private boolean isBatchRequest(HttpServletRequest request) {
return request.getRequestURI().startsWith("/api/v1/batch/");
}
private AuthOutcome auth(HttpFacade exchange) {
return extractToken(exchange.getRequest().getHeaders(HttpHeaders.AUTHORIZATION))
.map(token -> extractUserPw(token)
.filter(userpw -> verify(userpw.getFirst(), userpw.getSecond()))
.map(userpw -> AuthOutcome.AUTHENTICATED)
.orElse(AuthOutcome.FAILED))
.orElse(AuthOutcome.NOT_ATTEMPTED);
}
private Optional<String> extractToken(List<String> authHeaders) {
return authHeaders == null ? Optional.empty() : authHeaders.stream().map(authHeader -> authHeader.trim().split("\\s+"))
.filter(split -> split.length == 2)
.filter(split -> split[0].equalsIgnoreCase("Basic"))
.map(split -> split[1])
.findFirst();
}
private Optional<Pair<String, String>> extractUserPw(String token) {
try {
String userpw = new String(Base64.decode(token));
String[] parts = userpw.split(":");
if (parts.length == 2) {
return Optional.of(Pair.of(parts[0], parts[1]));
}
} catch (Exception e) {
log.debug("Basic Auth Token formatting error", e);
}
return Optional.empty();
}
private boolean verify(String user, String password) {
return (this.user.equals(user) && this.password.equals(password));
}
}
And finally the wrapped ServletRequest (as you cannot remove Headers from the request):
public class AuthentifiedHttpServletRequest extends HttpServletRequestWrapper {
public AuthentifiedHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
return true;
}
#Override
public String getAuthType() {
return "Basic";
}
#Override
public String getHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeader(name);
}
return null;
}
#Override
public Enumeration<String> getHeaders(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeaders(name);
}
return Collections.enumeration(Collections.emptyList());
}
#Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(EnumerationUtils.toList(super.getHeaderNames())
.stream()
.filter(s -> !HttpHeaders.AUTHORIZATION.equalsIgnoreCase(s))
.collect(Collectors.toList()));
}
#Override
public int getIntHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getIntHeader(name);
}
return -1;
}
}
Not quite sure whether this is still relevant or not, but maybe someone will find it helpful.
By default, Keycloak is overwriting plenty of configurations. It's intercepting all Auth request (OAuth2, BasicAuth etc.)
Fortunately, with Keycloak, it's possible to enable authentication both with OAuth2 and BasicAuth in parallel, which I assume is what you want to enable in your dev/localhost environments.
In order to do that, you first need to add the following property to your
application-local-auth.properties:
keycloak.enable-basic-auth=true
This property will enable Basic Auth in your dev environment. However, you also need to enable Basic Auth at your client in Keycloak.
You can accomplish that by connecting to the Keycloak Admin Console on your local Keycloak server and enabling the Direct Access Grant for your client:
Enabling Basic Auth in Keycloak
After that you can authenticate both with Bearer Token and Basic Auth.
I'm looking for better architecture solution. Currently we have following end-point:
/**
* Endpoint for frontend to be sure we are logged in
*/
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
This end-point is covered by Spring Security and only authenticated users have access to it.
What is the best practice of making frontend aware of user authentication state?
It looks like you are using pooling to check the login status. Your controller method
#RequestMapping(value = "/is_auth")
public boolean getAuth() {
return true;
}
will never return false. So in general there is no need to have a return value in this case.
#ResponseStatus(value = HttpStatus.OK)
#RequestMapping(value = "/is_auth")
public void ping() {
// log ?
}
I believe the best solution would be a websocket connection between client and server. If you then implement a SessionListener, you can very easy send a login status to corresponding client if his session get expired:
//
// pseudo code
//
#Component
public class SessionListener implements HttpSessionListener {
private static final Logger logger = LoggerFactory.getLogger(SessionListener.class);
#Autowired
private IWebsocketService websocketService; // you own service here
#Override
public void sessionCreated(HttpSessionEvent se) {
logger.debug("sessionCreated: {}", se.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
String sessionId = se.getSession().getId();
logger.debug("sessionDestroyed: {}", sessionId);
websocketService.sendLoginStatus(sessionId, false);
}
}
EDIT: here is a very good example how to implement websockets with spring and javascript: Using WebSocket to build an interactive web application