Maximum user sessions not working when user signs in Springboot/Security - java

I'm trying to add maximum sessions to my spring security context, however the settings are not taking effect. I've been following this code to implement JWT tokens on a spring backend using this tutorial.
I've added the relevant code I believe to the security config:
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
return filter;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.antMatchers("/api/configuration/**")
.permitAll()
.anyRequest()
.authenticated()
.and().sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry());
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
However when I try and login with a user - the user can login however many times they wish.
I tried looking in the user registry to see if there were multiple user principles being created for each login but there isn't.
I also tried to step through ConcurrentSessionControlAuthenticationStrategy.onAuthentication however that doesn't seem to be being called - but I have no idea why.
Edit:
Adding the customer AuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LogManager.getLogger(AuthController.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
log.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Sorry, You're not authorized to access this resource.");
}
}
EDIT:
The session is setting set correctly in the session registry now. However because my login endpoint has permitAll() (I think)
ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called and adding the newly created session to the registry.
I believe it might be something to do with the way my CustomAuthenticationProvider is setup:
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsernameOrEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
So signin is always allowing new logins as ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called after authentication.

Related

Adding an API key authentication along with form based login for same endpoints

I have a spring boot application secured using a simple form based login. There is a necessity to add an API key support. I have created datasource to store the key and link it with user account. I am not sure that I am adding the filter part for API key authentication correctly.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("test")
.password(passwordEncoder.encode("test")).roles("USER","ADMIN");
}
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
TokenAuthenticationFilter tokenAuthenticationFilter = new TokenAuthenticationFilter(authHeaderName);
tokenAuthenticationFilter.setAuthenticationManager(authentication -> {
String principal = (String) authentication.getPrincipal();
Optional<AuthToken> authTokenOptional = authTokenRepo.findByAuthKey(principal);
if (!authTokenOptional.isPresent())
{
throw new BadCredentialsException("The API key was not found "
+ "or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
});
httpSecurity.authorizeRequests()
.antMatchers("/login", "/userlogin/**")
.permitAll()
.antMatchers("/**")
.hasAnyRole("ADMIN", "USER")
.and()
.formLogin()
.successForwardUrl("/userlogin/success")
.failureForwardUrl("/userlogin/failure")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.permitAll()
.and()
.addFilterBefore(tokenAuthenticationFilter, AnonymousAuthenticationFilter.class)
.csrf()
.disable();
}
}
public class TokenAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
private final String authHeaderName;
public TokenAuthenticationFilter(String authHeaderName){
this.authHeaderName = authHeaderName;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(authHeaderName);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "Not available";
}
}
I am not sure if I'm adding the Filter in the correct order. I know I can create MultiHttpSecurityConfig, however unlike in MultiHttpSecurityConfig, would like to use same endpoints. I would like Spring Security to first try the API key, if not present use the form login.

Spring Web Security entry point triggers

I am getting started with Spring Web Security for my application and I am trying to implement stateless JWT authentication. Curretly, the configure method in the Web Security config is the following
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login", "/register", "/authenticate/{uuid}").permitAll()
.anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
My jwtAuthenticationEntryPoint is the following:
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
log.warn("Responding with unauthorized error. Message - {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Sorry, You're not authorized to access this resource.");
}
}
The authentication works correctly besides for the "/authenticate/{uuid}" endpoint. The request is allowed (Status 200 and correct return of the function) but I keep getting the warning from the jwtAuthenticationEntryPoint class ("Responding with unauthorized error") in the console.
Why is the EntryPoint getting triggered for that specific request and how can I resolve it?
EDIT:
AuthenticationController:
#RestController
#CrossOrigin
public class AuthenticationController {
#RequestMapping(value = "/authenticate/{uuid}", method = RequestMethod.GET)
public ResponseEntity<?> authenticate(#PathVariable String uuid){
return ResponseEntity.ok(uuid);
}
}
Pls use web.ignoring() to try as the below:
public void configure(WebSecurity web) throws Exception {
String [] notauthlist = new String[]{"/login", "/register","/authenticate/**"};
web.ignoring().antMatchers(notauthlist);
}
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
;
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}

Implement logout in Spring for Single Page Application

I'm using Java Web Token (JWT) for authentication in my web app. I want to create a /logout Rest endpoint that deletes the JSession cookie on the client side, invalidates the session AND does whatever else is necessary.
The front end is a SPA written in React.
I have the following configure method in the WebSecurityConfig class that extends WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.and()
.authorizeRequests()
.antMatchers("/signup").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
I had the following code, but it returns 404 Not found error with the path set to /login. I want to get a HTTP response of 200 and some cleanups instead. what should I do ?
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).logoutSuccessUrl("/login").invalidateHttpSession(true)
After some research I found out that if I want to use stateless Rest API design, I shouldn't use any cookie, including JSESSIONID.
Therefore I changed my code to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
Could you first try for login request like this. First add JWTLoginFilter in your WebSecurityConfig.
Pasting code from my sample project:
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
You wouldn't require CORSFilter if your front end and back end are on same server.
Also find below JWTLoginFilter class
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter
{
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(String url, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
AccountCredential class is simple POJO class containing two fields username and password, which I was using to receive the request.
Also please note that UsernamePasswordAuthenticationFilter that we are using require two fields in login request 'username' and 'password'. Like {"username":"user1","password":"secret1"}

How to restrict access to .html pages using Spring Boot REST

I'm trying to restrict access to a page called dashboard.html to unauthenticated users. So far, I've had no success. Here's my WebSecurityConfigurerAdapter:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**"
).permitAll()
.and()
.authorizeRequests()
.antMatchers("/dashboard/**", "/dashboard.html/**").authenticated()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new StatelessLoginFilter("/login", tokenAuthenticationService, userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout()
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterAfter(new CsrfTokenFilter(), CsrfFilter.class);
}
}
Whenever I have it set up this way, whenever I try to login, an infinite redirect loop is caused. The browser tries to navigate to dashboard.html but is restricted. This causes a redirect to the login page, which tries to redirect to the dashboard since there is a valid token.
If I have it set up like below, everyone can access dashboard.html and make calls to the /dashboard endpoint which is not desired:
http
.authorizeRequests()
.antMatchers("/index.html", "/",
"/login.html","/signup.html", "/videos/**",
"/login", "/logout", "/images/**", "/fonts/**",
"/css/**", "/js/**", "/pages/**", "/sass/**",
"/dashboard/**", "/dashboard.html/**").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
My login uses JWT tokens and uses the filter below to set the SecurityContext placeholder:
class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
private final TokenAuthenticationService tokenAuthenticationService;
private final CustomUserDetailsService userDetailsService;
protected StatelessLoginFilter(String urlMapping, TokenAuthenticationService tokenAuthenticationService,
CustomUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(urlMapping));
this.userDetailsService = userDetailsService;
this.tokenAuthenticationService = tokenAuthenticationService;
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
final BusinessUser user = new ObjectMapper().readValue(request.getInputStream(), BusinessUser.class);
final UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(
user.getEmail(), user.getPassword());
return getAuthenticationManager().authenticate(loginToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
final BusinessUser authenticatedUser = userDetailsService.loadUserByUsername(authentication.getName());
final UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
tokenAuthenticationService.addAuthentication(response, userAuthentication);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
I'm using the line SecurityContextHolder.getContext().setAuthentication(userAuthentication); to set the authentication. This works perfectly fine. If a user is found in the DB matching the credentials sent from the user, then the security context is usable to retrieve various data associated to the user.
MY QUESTION: How can I restrict the page dashboard.html and calls to the /dashboard endpoint to unauthenticated users (those without an authentication object inside the SecurityContextHolder)?
You can use a custom RequestMatcher in combination with denyAll. First, your custom matcher:
public class PermittedPagesMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest httpServletRequest) {
if (matchesToPaths(httpServletRequest,"/index.html", "/", "/login.html","/signup.html", "/videos/**", "/login", "/logout", "/images/**", "/fonts/**", "/css/**", "/js/**", "/pages/**", "/sass/**", "/dashboard/**", "/dashboard.html/**")) {
return true;
}
if (matchesToPaths(httpServletRequest, "/dashboard/**", "/dashboard.html/**")) {
return httpServletRequest.getUserPrincipal() == null;
}
return false;
}
private boolean matchesToPaths(HttpServletRequest httpServletRequest, String... paths) {
for (String p : paths) {
if (new AntPathRequestMatcher(p).matches(httpServletRequest)) {
return true;
}
}
return false;
}
}
This custom RequestMatcher filters your request to permitted pages to all of your default pages and the dashboard is only available if the request is not authenticated.
Second, combine the matcher and denyAll()
http
.authorizeRequests()
.requestMatchers(new PermittedPagesMatcher())
.permitAll()
.and()
.antMatchers("/dashboard/**", "/dashboard.html/**")
.denyAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
denyAll() ensures, that by default no one is allowed to access this page.
Attention: The order of permit and deny is important!

How to disable authentication for one service in Spring Boot?

I have a Spring Boot application, in which most of the pages are secured (you need to log in to access them) using following security configuration.
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
#Autowired
public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("myapp")
.roles("ADMIN")
.and()
.withUser("guest")
.password("guest")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/")
.permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private CsrfTokenRepository csrfTokenRepository() {
final HttpSessionCsrfTokenRepository repository =
new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
In my application class, I have a service publicData, which I want to be accessible without authentication (even, if the user isn't logged in).
#SpringBootApplication
#RestController
public class MyAppApplication {
#RequestMapping("/resource")
public Map<String,Object> home() {
final Map<String,Object> model = new HashMap<>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
#RequestMapping("/publicData")
public String publicData() {
return ...;
}
#RequestMapping("/user")
public Principal user(final Principal user) {
return user;
}
public static void main(final String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
How can I do this?
I tried
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/")
.permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.and()
.authorizeRequests()
.antMatchers("/publicData").permitAll();
}
but it didn't work.
You can create a role with all permission to access and grant that access just in some methods using Spring security annotation
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
Of course every user need to get this role automatically when he connect in your application.
<http use-expressions="true">
<intercept-url pattern="/*"
access="hasRole('admin')"/>
</http>
Then in your free access method
#PreAuthorize("hasRole('admin')")
public void create(Contact contact);

Categories

Resources