How to secure Vaadin flow application with Spring Security - java

I'm trying to integrate vaadin 10 with spring security (using the spring project base provided by vaadin), and I'm confused on how they interact exactly. If I go to a protected url (in this example, "/about") typing it directly in the browser, the login page shows up. If I go to the same URL by clicking in a link from the UI, the page shows up even if I'm not authenticated. So I guess that Vaadin is not going through Spring Security's filter chain, but then how do I secure my resources inside the UI, and how can I share the authenticated user between vaadin and spring? Am I supposed to implement security twice? The documentation available doesn't seem to cover this, and every link on the internet has examples with Vaadin 7-8, which I've never used and seems to work differently from 10+.
Does anyone know any resource about this, or can you enlighten me on how all of this works together so I can know what I'm doing?
Here's my security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] ALLOWED_GET_URLS = {
"/",
//"/about",
"/login/**",
"/frontend/**",
"/VAADIN/**",
"/favicon.ico"
};
private static final String[] ALLOWED_POST_URLS = {
"/"
};
//#formatter:off
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.mvcMatchers(HttpMethod.GET, ALLOWED_GET_URLS)
.permitAll()
.mvcMatchers(HttpMethod.POST, ALLOWED_POST_URLS)
.permitAll()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll();
}
//#formatter:on
}

Using Vaadin Flow (12.0.2), Spring Boot Starter (2.0.2.RELEASE) and Spring Boot Security, basically, I found authorizing based on role/authority using the following ways;
Route/Context based role/authority managment
Spring security (HttpSecurity)
Vaadin API (BeforeEnterListener and Route/Navigation API)
Business unit role/authority management
Inside the code using HttpServletRequest.isUserInRole method
Let's start with a simple example of Spring Security configuration;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig
extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // CSRF is handled by Vaadin: https://vaadin.com/framework/security
.exceptionHandling().accessDeniedPage("/accessDenied")
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and().logout().logoutSuccessUrl("/")
.and()
.authorizeRequests()
// allow Vaadin URLs and the login URL without authentication
.regexMatchers("/frontend/.*", "/VAADIN/.*", "/login.*", "/accessDenied").permitAll()
.regexMatchers(HttpMethod.POST, "/\\?v-r=.*").permitAll()
// deny any other URL until authenticated
.antMatchers("/**").fullyAuthenticated()
/*
Note that anonymous authentication is enabled by default, therefore;
SecurityContextHolder.getContext().getAuthentication().isAuthenticated() always will return true.
Look at LoginView.beforeEnter method.
more info: https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/anonymous.html
*/
;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password("$2a$10$obstjyWMAVfsNoKisfyCjO/DNfO9OoMOKNt5a6GRlVS7XNUzYuUbO").roles("ADMIN");// user and pass: admin
}
/**
* Expose the AuthenticationManager (to be used in LoginView)
* #return
* #throws Exception
*/
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
As you see, I have not specified any permission based on role on any of my routed views (annotated with #Route) yet. What I will do is if I have a routed view, I will register a BeforeEnterListener when it (the routed view) is being constructed and will check the required role/privilege there.
The following is an example to check if the user has ADMIN role before navigating to admin-utils view;
#Route(value = "admin-utils")
public class AdminUtilsView extends VerticalLayout {
#Autowired
private HttpServletRequest req;
...
AdminUtilsView() {
...
UI.getCurrent().addBeforeEnterListener(new BeforeEnterListener() {
#Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
if (beforeEnterEvent.getNavigationTarget() != DeniedAccessView.class && // This is to avoid a
// loop if DeniedAccessView is the target
!req.isUserInRole("ADMIN")) {
beforeEnterEvent.rerouteTo(DeniedAccessView.class);
}
}
});
}
}
In case the user has not the ADMIN role, (s)he will be routed to DeniedAccessView which is permitted already for all in the Spring Security configuration.
#Route(value = "accessDenied")
public class DeniedAccessView
extends VerticalLayout {
DeniedAccessView() {
FormLayout formLayout = new FormLayout();
formLayout.add(new Label("Access denied!"));
add(formLayout);
}
}
In the above example (AdminUtilsView ), you can also see a use case for HttpServletRequest.isUserInRole() in Vaadin code by autowiring the HttpServletRequest.
SUMMARY: If your view has a Route, use BeforeEnterListener to Authorize the request first, otherwise use Spring Security
matchers (e.g. regexMatchers or antMatchers) for rest services and etc. .
NOTE: Using both the Vaadin Route and Spring Security matcher rules together for the same rule might be a bit twisted and I don't suggest that (it causes some internal loops in Vaadin; e.g. imagine we have a view routed with /view and an entry in Spring Security for /view with a required role. If a user is missing such role and (s)he is routed/navigated to such page (using Vaadin routing API), Vaadin tries to open the view associated with the route while Spring security avoids that due to the missing role).
Also, I think, using Vaadin flow navigation API a good practice before rerouting or navigating the user to a different view/context would be to check for the required role/authority.
Moreover, to have an example of using AuthenticationManager in Vaadin, we can have a Vaadin based LoginView similar to;
#Route(value = "login")
public class LoginView
extends FlexLayout implements BeforeEnterObserver {
private final Label label;
private final TextField userNameTextField;
private final PasswordField passwordField;
/**
* AuthenticationManager is already exposed in WebSecurityConfig
*/
#Autowired
private AuthenticationManager authManager;
#Autowired
private HttpServletRequest req;
LoginView() {
label = new Label("Please login...");
userNameTextField = new TextField();
userNameTextField.setPlaceholder("Username");
UiUtils.makeFirstInputTextAutoFocus(Collections.singletonList(userNameTextField));
passwordField = new PasswordField();
passwordField.setPlaceholder("Password");
passwordField.addKeyDownListener(Key.ENTER, (ComponentEventListener<KeyDownEvent>) keyDownEvent -> authenticateAndNavigate());
Button submitButton = new Button("Login");
submitButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
authenticateAndNavigate();
});
FormLayout formLayout = new FormLayout();
formLayout.add(label, userNameTextField, passwordField, submitButton);
add(formLayout);
// center the form
setAlignItems(Alignment.CENTER);
this.getElement().getStyle().set("height", "100%");
this.getElement().getStyle().set("justify-content", "center");
}
private void authenticateAndNavigate() {
/*
Set an authenticated user in Spring Security and Spring MVC
spring-security
*/
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(userNameTextField.getValue(), passwordField.getValue());
try {
// Set authentication
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
/*
Navigate to the requested page:
This is to redirect a user back to the originally requested URL – after they log in as we are not using
Spring's AuthenticationSuccessHandler.
*/
HttpSession session = req.getSession(false);
DefaultSavedRequest savedRequest = (DefaultSavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
String requestedURI = savedRequest != null ? savedRequest.getRequestURI() : Application.APP_URL;
this.getUI().ifPresent(ui -> ui.navigate(StringUtils.removeStart(requestedURI, "/")));
} catch (BadCredentialsException e) {
label.setText("Invalid username or password. Please try again.");
}
}
/**
* This is to redirect user to the main URL context if (s)he has already logged in and tries to open /login
*
* #param beforeEnterEvent
*/
#Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//Anonymous Authentication is enabled in our Spring Security conf
if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
//https://vaadin.com/docs/flow/routing/tutorial-routing-lifecycle.html
beforeEnterEvent.rerouteTo("");
}
}
}
And finally, here is the logout method that can be called from a menu or button:
/**
* log out the current user using Spring security and Vaadin session management
*/
void requestLogout() {
//https://stackoverflow.com/a/5727444/1572286
SecurityContextHolder.clearContext();
req.getSession(false).invalidate();
// And this is similar to how logout is handled in Vaadin 8:
// https://vaadin.com/docs/v8/framework/articles/HandlingLogout.html
UI.getCurrent().getSession().close();
UI.getCurrent().getPage().reload();// to redirect user to the login page
}
You can continue completing the role management using Spring UserDetailsService and creating a PasswordEncoder bean by looking at the following examples:
https://github.com/igor-baiborodine/vaadin-demo-bakery-app/blob/master/src/main/java/com/kiroule/vaadin/bakeryapp/app/security/SecurityConfiguration.java
https://github.com/igor-baiborodine/vaadin-demo-bakery-app/blob/master/src/main/java/com/kiroule/vaadin/bakeryapp/app/security/UserDetailsServiceImpl.java
https://www.baeldung.com/role-and-privilege-for-spring-security-registration

Related

Vaadin gets stuck in loop when using Spring security with a login form

Vaadin 19 + Spring Boot + custom authentication flow
Working: login shown correctly + authentication succeeds + redirect to correct home page URL
Problem: on the homepage the login box is shown again
My implementation is based on https://vaadin.com/learn/tutorials/securing-your-app-with-spring-security/setting-up-spring-security
#Autowired
private UserService userService;
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider(userService))
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Require login to access internal pages and configure login form.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Not using Spring CSRF here to be able to use plain HTML for the login page
http.csrf().disable()
// Register our CustomRequestCache that saves unauthorized access attempts, so
// the user is redirected after login.
.requestCache().requestCache(new CustomRequestCache())
// Restrict access to our application.
.and().authorizeRequests()
// Allow all flow internal requests.
.requestMatchers(SecurityUtil::isFrameworkInternalRequest).permitAll()
// Allow all requests by logged in users.
.anyRequest().authenticated()
// Configure the login page.
.and()
.formLogin()
.loginPage(URL_LOGIN).permitAll()
.loginProcessingUrl(URL_LOGIN_PROCESSING)
.failureUrl(URL_LOGIN_FAILURE)
.successHandler(new CustomAuthenticationSuccessHandler())
// Configure logout
.and()
.logout()
.logoutUrl(URL_LOGOUT)
.logoutSuccessUrl(URL_LOGOUT_SUCCESS);
}
/**
* Allows access to static resources, bypassing Spring security.
*/
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
// Vaadin Flow static resources
"/VAADIN/**",
// the standard favicon URI
"/favicon.ico",
// the robots exclusion standard
"/robots.txt",
// style related files, needed here to prevent download of files between login and home
"/icons/**",
"/images/**",
"/fonts/**",
"/styles/**",
"/sw-runtime-resources-precache.js",
// web application manifest
"/manifest.webmanifest",
"/sw.js",
"/offline-page.html",
// (development mode) static resources
"/frontend/**",
// (development mode) webjars
"/webjars/**",
// (production mode) static resources
"/frontend-es5/**", "/frontend-es6/**");
}
The logging shows repeated calls to CustomAuthenticationProvider to this method:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
Could this be a missing user role? Does this need to be configured?
My application worked OK before, when I was first testing with a single fixed account like this:
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("username")
.password("{noop}password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
Check if you exclude the LoginView in ConfigureUIServiceInitListener.java
private void beforeEnter(BeforeEnterEvent event) {
if (!LoginView.class.equals(event.getNavigationTarget()) // <--
&& !SecurityUtils.isUserLoggedIn()) {
event.rerouteTo(LoginView.class);
}
}
As I already suspected, problem was a missing ROLE.
In my CustomAuthenticationProvider, I was returning a user like this:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var email = authentication.getName();
var password = authentication.getCredentials().toString();
// Call here to backend to check login
return new UsernamePasswordAuthenticationToken(email, password);
}
Which actually returns a user which is authenticated=false...
By changing the code to:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var email = authentication.getName();
var password = authentication.getCredentials().toString();
// Call here to backend to check login
Collection<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority("USER"));
return new UsernamePasswordAuthenticationToken(email, password, roles);
}
the user has authenticated=true, and the Vaadin UI works as expected.

Microservices Spring Cloud Gateway + Spring Security LDAP as SSO + JWT - Token lost between request/response

I am developing a microservice ecosystem using spring-boot. The microservices which are in place at the moment :
Spring Cloud Gateway - Zuul (responsible also for authorization requests downstream for microservices - extracting tokens from requests and validates whether the user has the right role to perform requests),
SSO using spring security LDAP ( responsible for authenticate user and generate JWT tokens) , SSO has also just a login page using thymeleaf
Web interface using Thymeleaf without login page ( not sure if I should use here spring security, at the moment)
Another microservice which provides data to web ui based on request from the browser
Discovery services using Eureka
The idea is filtering all the requests on the gateway for validating and forward the requests. If the user is not authenticated or token is experied then forward the user to SSO for login.
The firewall will expose only the port on Gateway side then others one will be theirs ports blocked using firewall rules.
Now i am blocked without knowing where to go or if I should move the SSO together with the gateway ( conceptually wrong but it might be a workaround if i do not find any solution)
Following the issue : The user hits the gateway (ex. http://localhost:7070/web) then the gateway forward the user to (ex. http://localhost:8080/sso/login), after the credentials have been validated , the SSO creates the JWT tokens and add it to the Header of response.
Afterwards the SSO redirect the request back to the gateway (ex. http://localhost:7070/web).
Until here, everything works fine but when the request reaches the gateway there is no 'Authorization' header on request which means NO JWT token.
So the gateway should extract the token, check the credentials and forward the request to the Web interface (ex. http://localhost:9090)
I am aware that using Handler on SSO to redirect request won't work at all due to 'Redirect' from spring will remove the token from the header before redirect.
But I do not know whether there is another way to set again the JWT on the header after Spring has removed it from the request or not.
Is there any conceptually issue on the architecture side? How can I forward the JWT to the gateway for being checked?
SSO
#EnableWebSecurity
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.url}")
private String ldapUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// Stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/login")
// Add a handler to add token in the response header and forward the response
.successHandler(jwtAuthenticationSuccessHandler())
.failureUrl("/login?error")
.permitAll()
.and()
// handle an authorized attempts
.exceptionHandling()
.accessDeniedPage("/login?error")
.and()
.authorizeRequests()
.antMatchers( "/dist/**", "/plugins/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.userSearchFilter("uid={0}")
.groupSearchBase("ou=groups")
.groupSearchFilter("uniqueMember={0}")
.contextSource()
.url(ldapUrl);
}
#Bean
public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
return new JwtAuthenticationSuccessHandler();
}
}
public class JwtAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private JwtConfig jwtConfig;
#Autowired
private JwtTokenService jwtTokenService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
String token = jwtTokenService.expiring(ImmutableMap.of(
"email", auth.getName(),
"authorities", auth.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.map(Object::toString)
.collect(Collectors.joining(","))));
response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if(defaultSavedRequest != null){
getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
}else{
getRedirectStrategy().sendRedirect(request, response, "http://localhost:7070/web");
}
}
}
Gateway
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtConfig jwtConfig;
#Value("${accessDeniedPage.url}")
private String accessDeniedUrl;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable CSRF (cross site request forgery)
// we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/sso/login")
.permitAll()
.and()
// handle an authorized attempts
// If a user try to access a resource without having enough permissions
.exceptionHandling()
.accessDeniedPage(accessDeniedUrl)
//.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
// Add a filter to validate the tokens with every request
.addFilterBefore(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
.antMatchers("/web/**").hasAuthority("ADMIN")
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
#RequiredArgsConstructor
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(jwtConfig.getHeader());
// 2. validate the header and check the prefix
if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response); // If not valid, go to the next filter.
return;
}
// If there is no token provided and hence the user won't be authenticated.
// It's Ok. Maybe the user accessing a public path or asking for a token.
// All secured paths that needs a token are already defined and secured in config class.
// And If user tried to access without access token, then he/she won't be authenticated and an exception will be thrown.
// 3. Get the token
String token = header.replace(jwtConfig.getPrefix(), "");
try { // exceptions might be thrown in creating the claims if for example the token is expired
// 4. Validate the token
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String email = claims.get("email").toString();
if(email != null) {
String[] authorities = ((String) claims.get("authorities")).split(",");
final List<String> listAuthorities = Arrays.stream(authorities).collect(Collectors.toList());
// 5. Create auth object
// UsernamePasswordAuthenticationToken: A built-in object, used by spring to represent the current authenticated / being authenticated user.
// It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
email, null, listAuthorities
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
// 6. Authenticate the user
// Now, user is authenticated
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// In case of failure. Make sure it's clear; so guarantee user won't be authenticated
SecurityContextHolder.clearContext();
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
#Component
public class AuthenticatedFilter extends ZuulFilter {
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
final Object object = SecurityContextHolder.getContext().getAuthentication();
if (object == null || !(object instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
final UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
final RequestContext requestContext = RequestContext.getCurrentContext();
/*
final AuthenticationDto authenticationDto = new AuthenticationDto();
authenticationDto.setEmail(user.getPrincipal().toString());
authenticationDto.setAuthenticated(true);
authenticationDto.setRoles(user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())); */
try {
//requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString(authenticationDto));
requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString("authenticationDto"));
} catch (JsonProcessingException e) {
throw new ZuulException("Error on JSON processing", 500, "Parsing JSON");
}
return null;
}
}
There is an issue about JWT. It is called "Logout Problem". First you need to understand what it is.
Then, check TokenRelay filter (TokenRelayGatewayFilterFactory) which is responsible for passing authorization header to downstream.
If you look at that filter, you will see that JWTs are stored in ConcurrentHashMap (InMemoryReactiveOAuth2AuthorizedClientService). The key is session, the value is JWT. So, session-id is returned instead of JWT header as the response provided.
Until here, everything works fine but when the request reaches the
gateway there is no 'Authorization' header on request which means NO
JWT token.
Yes. When the request comes to gateway, TokenRelay filter takes session-id from request and find JWT from ConcurrentHashMap, then it passes to Authorization header during downstream.
Probably, this flow is designed by spring security team to address JWT logout problem.

Unable to understand the behavior of Spring security

I am using spring boot 2.1.4 with dependencies of actuator. I wanted to configure separate authentication and authorization mechanisms for actuator and my application. I read the Multiple HttpSecurity and configured my WebSecurityAdapter as follows:
#Configuration
public class ProvisioningServiceSecurityConfiguration {
#Value("${actuator.user.name}")
private String actuatorUserName;
#Value("${actuator.password}")
private String actuatorPassword;
#Value("${actuator.role}")
private String actuatorRole;
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("ADMIN").build());
manager.createUser(
users.username(actuatorUserName).password(actuatorPassword).roles(actuatorRole).build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/locations/**")
.antMatcher("/organizations/**")
.antMatcher("/productTypes/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/**")
.authorizeRequests()
.anyRequest().hasRole("ACTUATOR_ADMIN")
.and()
.httpBasic();
}
}
/*#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}*/
}
Note: I have disabled form Login temporarily
When I run a curl request
curl -XGET http://localhost:9797/provisioningService/organizations/all
I am able to see the output. Its as though the spring security never existed. When I enable form login, I get the spring login screen. The other behavior that I observed is if I interchange the username and password of /locations with the actuator username and password, I still get a valid response back.
I understand the form login is more of a fallback but I want to disable the form login (probably we may move to cas) and use authentication and authorization only based on the spring security httpBasic. I am not able to understand the mistake I am making.
My requirement is finally :
1) a request to /organizations or /locations etc should be accessible only if the username password is "user" and "password"
2) a request to /manage which is the actuator api should be accessible only if the username and password and role matches with the actuator username and password.
3) Any other API can be permitAll / form login
How do i go about achieving this?
1) Spring Security has a function to control access by filtering by Authorities(after Authentication), but there is no function to filter by the information required for login. You need business logic to verify that you are attempting to log in with the corresponding ID and password during login.
2) As mentioned above, access control with ID and password is not provided.
I recommend creating Authorities for only the two accounts you requested.
3) .antMatcher("/form").permitAll()

Authentication using cookies in spring boot

//Part of my Controller class
#RequestMapping("/login")
public String login(HttpServletRequest request,HttpServletResponse response) {
request.setAttribute("mode", "MODE_LOGIN");
return "welcomepage";
}
#RequestMapping ("/login-user")
public String loginUser(#ModelAttribute User user, HttpServletRequest request,HttpServletResponse response) {
if((userService.findByUsernameAndPassword(user.getUsername(), user.getPassword())!=null)) {
Cookie loginCookie=new Cookie("mouni","user.getUsername()");
loginCookie.setMaxAge(30*5);
response.addCookie(loginCookie);
return "homepage";
}
else {
request.setAttribute("error", "Invalid Username or Password");
request.setAttribute("mode", "MODE_LOGIN");
return "welcomepage";
}
}
I am doing a library management project on java spring boot.
I have one problem, i would like to do authentication using cookies.
In brief, Once after user logged in with his credentials, username should be saved as cookie value. Next time when user is going to login, he can just enter username and should be logged in successfully.
Could someone please help me out
Since security is a complex matter, I recommend using Spring Security, even though you're tasked to do it without. To illustrate the complexity about security, I can already tell you that your current code has a vulnerability, since you're trusting a plaintext username cookie as your sole authentication. Spring Security on the other hand uses a key to generate a remember me cookie so that it is much more difficult to impersonate someone (unless you know the key).
So, if you would be using Spring Security, the first thing you need to do is to create a UserDetailsService, which has a method called loadByUsername(). To implement this, you could use your UserService and use the User builder to construct a Spring Security user object:
public class MyUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("admin".equalsIgnoreCase(username)) {
return User.builder()
.username(username)
// This should contain the hashed password for the requested user
.password("$2a$10$T5viXrOTIkraRe2mZPyZH.MAqKaR6x38L.rbmRp53yQ8R/cFrJkda")
// If you don't need roles, just provide a default one, eg. "USER"
.roles("USER", "ADMIN")
.build();
} else {
// Throw this exception if the user was not found
throw new UsernameNotFoundException("User not found");
}
}
Be aware, in contrary to your original UserService.findByUsernameAndPassword() you do not have to check the password by yourself, just retrieve the user object and pass the hashed password.
The next step is to provide a proper PasswordEncoder bean. In my example I'm using BCrypt with 10 rotations, so I created the following bean:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
public UserDetailsService userDetailsService() {
return new MyUserDetailsService();
}
}
The next step is to configure Spring Security. For example:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html").permitAll()
.loginProcessingUrl("/login-user").permitAll().usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/welcome.html")
.and()
.rememberMe()
.alwaysRemember(true)
.tokenValiditySeconds(30*5)
.rememberMeCookieName("mouni")
.key("somesecret")
.and()
.csrf().disable();
}
In this case, all endpoints (/**) will be secured, you'll have a login form at login.html containing two form fields (username and password). The destination of the form should be /login-user and when a user is successfully logged in, he will be redirected to /welcome.html.
Similar to what you wrote in your code, this will generate a cookie called mouni containing a value (no longer a plain username) and it will be valid for 150 seconds, just like in your example.
I'm disabling CSRF here because I'm using a simple HTML form, and otherwise I would have to add a templating engine to pass the CSRF key. Ideally, you should keep this enabled.
You are using Spring framework which has the capability for the same which you are trying to achieve. so why to do it manually?
Have a look at spring security.
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/

Vaadin and Spring, authentication against Active Directory via LDAP

I am trying to authenticate against AD in my application created with Vaadin, which is using also Spring (SpringVaadinIntegration).
I can't find any information about how to achieve this and a lot of confusing, different and partial ways to connect to Active Directory with Spring security.
Since Vaadin form fields don't have a name, I don't know if I can even use a normal form or I have to write my own JSP. My impression is that to map the username and the password entered in the form to the xml it's necessary that the fields have a name.
Has anybody achieved this or anybody has a clue on how to do it?
If somebody can provide a link where this is explained step by step, for dummies, would be great too. I just can find partial solutions, where you don't get an overall of the system and how should be configured.
We have a TextField (username), a PasswordField (password) and a Button on a UI:
public class MyUI extends UI {
#Override
protected void init( VaadinRequest request ) {
setContent( VaadinSession.getCurrent().getAttribute("userId") == null ? getNewLoginLayout() : getNewMainLayout() );
}
private VerticalLayout getNewLoginLayout() {
TextField username = ...
TextField password = ...
Button login = ...
return new VerticalLayout(username, password, login);
}
}
When the button pushed we do a simple LDAP search like this on the server side (for example pass these parameters to a Spring bean). If it is successful we set a VaadinSession attribute (userId) and change the UI content to the main layout. Spring security need not necessarily.
Even this question is already answered I want to show you my solution.
We use Spring Security for LDAP authentication, so we have these two configuration classes:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
// #formatter:off
http
.authorizeRequests()
.anyRequest().authenticated() // Alle Requests erfordern einen Login...
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/#!").permitAll() // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-form
.and()
.logout().permitAll() // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-logout
.and()
.csrf().disable(); // CSRF (https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html) wird von Vaadin selbst gehandhabt!
// #formatter:on
}
/**
* #see http://stackoverflow.com/questions/34944617/java-config-for-spring-security-with-vaadin/35212403#35212403
*/
#Override
public void configure(WebSecurity web) throws Exception
{
// #formatter:off
web
.ignoring()
.antMatchers("/resources/**", "/VAADIN/**");
// #formatter:on
}
}
#Configuration
public class SecurityConfigActiveDirectory
{
#Value("${ldap.url}")
String ldapUrl;
#Value("${ldap.domain}")
String ldapDomain;
#Bean
public AuthenticationManager authenticationManager()
{
ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain, ldapUrl);
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
adProvider.setAuthoritiesMapper(getAuthorityMapper());
return new ProviderManager(Arrays.asList(adProvider));
}
private static SimpleAuthorityMapper getAuthorityMapper()
{
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
}
SecurityConfig class defines which pages should be protected in our web application and SecurityConfigActiveDirectory defines the LDAP authentication provider.
ldap.domain can be something like private.myTest.de and ldap.url something like ldap://myLdapHost.private.myTest.de:389.
Cheers!

Categories

Resources