spring security every url is a blank page - java

Here's my security config :
#Configuration
#EnableWebSecurity
#ComponentScan("lt.nortal.lab.web.security")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Override
public void configure(final WebSecurity web) throws Exception {
// Allow static resources to be served
web.ignoring().antMatchers("/css**", "/js**", "/html**", "/bootstrap");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf()
.and()
.authorizeRequests()
.antMatchers("/admin**").hasAuthority("admin") // allow public pages
.antMatchers("/login**").permitAll()
.anyRequest().authenticated() // other pages - authenticated only
.and()
.formLogin() // generate login form
.loginPage("/login")
.permitAll() // permit all to access login form (logical)
.and()
.logout().logoutSuccessUrl("/").permitAll(); // Permit all to access logout url
// (logical)
}
#Override
protected void registerAuthentication(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
Here's my authetntication provider :
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private LoginService loginService;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String email = authentication.getName();
String pass = (String) authentication.getCredentials();
User user = loginService.login(email);
if (user == null) {
throw new BadCredentialsException("Invalid email.");
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("user"));
if (user.getRole() == Role.ADMIN) {
authorities.add(new SimpleGrantedAuthority("admin"));
}
return new CustomAuthenticationToken(new AuthenticatedUser(user.getId(), user.getEmail()),
authorities);
}
#Override
public boolean supports(final Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
login controller:
#Controller
public class LoginLogoutController {
private static final Logger log = LoggerFactory.getLogger(LoginLogoutController.class);
#Autowired
private CurrentUser currentUser;
#Autowired
private LoginService loginService;
/**
* Represents user login form.
*
*/
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(final ModelMap model) {
// add login form attribute
model.put("loginForm", new LoginForm());
return "login";
}
/**
* Processes login form.
*
*/
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String processLogin(
final ModelMap model,
final #Valid #ModelAttribute("loginForm") LoginForm loginForm,
final BindingResult bindingResult) {
User user = null;
// lets check for errors
if (!bindingResult.hasErrors()) {
// no errors, lets try to login user.
user = loginService.login(loginForm.getEmail());
if (user == null) {
// something has failed, reject it with a global errror.
bindingResult.reject("login-generic-fail");
}
}
// at this point, we should have a user. If no user - return same login form.
if (user == null) {
return "login";
}
return "redirect:/";
}
}
When I start the server and go to any page I get a blank page, empty html file. I cant seem to figure out whats wrong here. If you need anything else please let me know

Related

Receiving not found error when registration process in SpringBoot app despite of correct URI

im in a very early developing of this app made in SpringBoot and Mysql with front of Angular , and trying to implement on my security process the register or creation of user , when i check on my swagger or postman i receive this json error:
{
"timestamp": "2020-11-03T17:53:58.048+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/login/renter"
}
On my humble logic that makes me think i got some miss-typed URI, but thats not the case.
Here some of the process
SECURITY CONFIGURATION
package com.example.demo.security;
....some imports....
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
RenterService renterService;
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/v2/api-docs",
"/register/renter",THIS ONE IS WHAT IM TRYING TO REACH AT
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login/renter")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}//permissions per endpoint
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(renterService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
}
For some reason just got me in the login page('login/renter'), not recognizing the url permitted formerly
Then my controller(In this case im trying to register a user thus the controller im exposing is the one referring to that process
REGISTER CONTROLLER
package com.example.demo.controller;
.....some imports....
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
public class RegistrationController {
#Autowired
RenterService renterService;
#ModelAttribute("renterCreated")
public RenterRegisterDto renterRegisterDto() {
return new RenterRegisterDto();
}
#GetMapping
public String showRegistrationFormRenter(Model model) {
return "registration";
}
public static final Logger logger = LoggerFactory.getLogger(RegistrationController.class);
#RequestMapping(value = "register/renter", method = RequestMethod.POST)
public String createUser(#ModelAttribute("renterCreated") #Valid RenterRegisterDto renterCreated, BindingResult result) throws GeneralException {
Role newRole = new Role();
newRole.setRoleType("User");
if (renterService.findByEmail(renterCreated.getRenterEmail()) != null) {
logger.error("user with email" + renterCreated.getRenterEmail() + "is already registered");
result.rejectValue("email", null, "There is already an account with this email");
}
if (result.hasErrors()) {
return "registration";
}
renterService.save(renterCreated);
return "userCreated";
}
}
On my service literally trigger the process of creating a user , asigning it a role and encryptying his password
RENTER SERVICE
public interface RenterService extends UserDetailsService {
Renter findByEmail(String renterEmail) throws GeneralException;
Renter findRenterById(Long id)throws GeneralException;
Renter save(RenterRegisterDto registerDto);
}
RENTER SERVICE IMPLEMENTATION
#Service
public class RenterServiceImpl implements RenterService {
#Autowired
private RenterRepository renterRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
public Renter findByEmail(String renterEmail) throws GeneralException {
return renterRepository.findByRenterEmail(renterEmail).orElseThrow(() -> new NotFoundException("SNOT_404_1", "EmailNotFound"));
}
public Renter findRenterById(Long id) throws GeneralException {
return renterRepository.findById(id).orElseThrow(() -> new NotFoundException("SNOT", "Id not found"));
}
public Renter save(RenterRegisterDto registerDto) {
Renter renter = new Renter();
Role role = new Role();
renter.setRenterName(registerDto.getRenterName());
renter.setRenterEmail(registerDto.getRenterEmail());
renter.setRenterPassword(passwordEncoder.encode((registerDto.getRenterPassword())));
renter.setRole(new Role(role.getRoleType()));
return renterRepository.save(renter);
}
public UserDetails loadUserByUsername(String renterEmail) throws UsernameNotFoundException {
Renter renter = renterRepository.findByRenterEmail(renterEmail).orElseThrow(() -> new UsernameNotFoundException("Invalid username or password."));
return new org.springframework.security.core.userdetails.User(renter.getRenbterEmail(),renter.getRenterPassword(),mapRoleUser(Arrays.asList(renter.getRole().getRoleType())));
}
private static List<GrantedAuthority> mapRoleUser(List<String> roles){
List<GrantedAuthority>authorities=new ArrayList<>();
for (String role : roles){
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
And the RegisterUserDto class just would set the string variables referring every field of the sign up form neccesary in the process to be sent to the Back(i won't expose all the code cause is already too much )
As far as i could humbly understand guess the problem would be located in my controller registration process , specifically the endpoint , and its permission in the spring security congifuration(which by the way i double checked are the same url) reason why when i try to trigger the process aiming to that endpoint pasing all the data automatically refuses it and send me to the login page with endpoint(login/renter)
Any idea about why is this happening?
Thanks in advance!!!

Create JWT Token within UsernamePasswordAuthenticationFilter or Controller?

I try to generate a JWT Token with Spring Security but I don't how to do it correctly (with the best practice).
Do I should "intercept" the authenticate method within UsernamePasswordAuthenticationFilter somewhere and generate token inside it ?
Or it is better to use AuthenticationManager autowired in the controller '/login' ?
I'm afraid to authenticate the user twice if I use the controller mechanism.
I used this tutorial : tutorial Jwt Token
Here is my code :
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
// #Autowired
// private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**", "/login/**", "/register/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
.formLogin()
.usernameParameter("email")
//.loginPage("http://localhost:4200/login").failureUrl("/login-error")
.and()
.logout()
.permitAll();
http
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public CustomDaoAuthenticationProvider authenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebConfig() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "Authorization", "Cache-Control",
"Access-Control-Allow-Origin")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.allowCredentials(true).maxAge(3600);
}
};
}
}
Token Filter
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(req, res);
}
}
Token Provider
#Component
public class JwtTokenProvider {
#Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";
#Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h
#Autowired
private UserDetailsService userDetailsService;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
Given your context, the controller is responsible for issuing a new token (after validating credentials) while the filter is responsible for authenticating the user against the given token. The controller should not populate the security context (authenticate user), it is the filter's responsibility.
To better understand the two phases:
Spring uses two filters to authenticate and log in a user.
See UsernamePasswordAuthenticationFilter and SecurityContextPersistenceFilter in a "username/password" scenario, from the Spring Security project: the first one processes an authentication attempt (username/password) while the latter populates the security context from a SecurityContextRepository (from a session in general).

Spring MVC, Spring Security: Unable to load login template

When to access any route, I get redirected to my login page, including accessing my login page, which is expected. Now the problem is the login page does not even load. I have configured the controller method, hander, template configuration, and security configuration necessary to make this work, but all I get is the error below:
http://localhost:8080/login?errorMessage=Login+to+use+the+app
UserController.java
#Controller
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/signup")
public String signup(Model model) {
model.addAttribute("user", new User());
return "signup";
}
#PostMapping("/users")
public String createUser(User user) {
// only create user if it does not exist
if (userService.findByUsername(user.getUsername()) == null) {
user.setRoles(new String[] {"ROLE_USER"});
userService.save(user);
return "redirect:/login";
}
else {
return "redirect:/signup";
}
}
#GetMapping("/login")
public String login(Model model) {
model.addAttribute("user", new User());
return "login";
}
#GetMapping("/profile")
public String currentUserProfile(Model model) {
User currentUser = (User) model.asMap().get("currentUser");
model.addAttribute("user", currentUser);
model.addAttribute("authorized", true);
return "profile";
}
#GetMapping("/users/{id}")
public String userProfile(#PathVariable Long id, Model model) {
User queriedUser = userService.findOne(id);
model.addAttribute("user", queriedUser);
User currentUser = (User) model.asMap().get("currentUser");
if (currentUser != null && currentUser.isAdmin()) {
model.addAttribute("authorized", true);
}
return "profile";
}
}
UserHandler.java
#ControllerAdvice(basePackages = "com.valencra.recipes.web.controller")
public class UserHandler {
public static final String USERNAME_NOT_FOUND_ERR_MSG = "Unable to find username";
public static final String ACCESS_DENIED_ERR_MSG = "Login to use the app";
#Autowired
private UserService userService;
#ModelAttribute("authenticatedUser")
public User addAuthenticatedUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String username = authentication.getName();
User user = userService.findByUsername(username);
if (user != null) {
return user;
}
else {
throw new UsernameNotFoundException(USERNAME_NOT_FOUND_ERR_MSG);
}
}
else {
throw new AccessDeniedException(ACCESS_DENIED_ERR_MSG);
}
}
#ExceptionHandler(AccessDeniedException.class)
public String redirectIfUserNotAuthenticated(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", ACCESS_DENIED_ERR_MSG);
return "redirect:/login";
}
#ExceptionHandler(UsernameNotFoundException.class)
public String redirectIfUserNotFound(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", USERNAME_NOT_FOUND_ERR_MSG);
return "redirect:/login";
}
}
TemplateConfig.java
#Configuration
public class TemplateConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("LEGACYHTML5");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(templateResolver());
springTemplateEngine.addDialect(new SpringSecurityDialect());
return springTemplateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver() {
final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RecipesAppUserDetailsService recipesAppUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(recipesAppUserDetailsService)
.passwordEncoder(User.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/signup").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/login")
.and()
.csrf().disable();
http.headers().frameOptions().disable();
}
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> response.sendRedirect("/");
}
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) ->
response.sendRedirect("/login");
}
#Bean
public EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public Object getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
};
}
}
application.properties
# Package where our entities (models) are located
recipes.entity.package = com.valencra.recipes.model
# Details for our datasource
recipes.db.driver = org.h2.Driver
recipes.db.url = jdbc:h2:mem:recipes
# Hibernate properties
hibernate.dialect = org.hibernate.dialect.H2Dialect
hibernate.implicit_naming_strategy = org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
hibernate.format_sql = true
hibernate.show_sql = true
hibernate.hbm2ddl.auto = create-drop
spring.data.rest.basePath=/api/v1
resources directory
EDIT: See #lgaleazzi's answer, and the comments that follow. Essentially, removing the UserHandler class fixes it.
What does the stack trace say?
Looking at your UserHandler, you handle a null authenticated object, and a user you can find. You don't deal with what happens with an anonymous user. I think that's the issue.
If nobody is authenticated, you'll get an authentication object with an anonymous user. You can check that with the method isAuthenticated(). But you don't actually have to write this code, Spring Boot handles all this pretty well with its default configuration.
Try adding the following method to SecurityConfig:
#Override
public void configure(WebSecurity web) throws Exception {
// configuring here URLs for which security filters
// will be disabled (this is equivalent to using
// security="none")
web.ignoring().antMatchers("/login");
}
There you can specify URLs in your application for which authentication should not be applied (also useful for static resources).
In your case, /login is not excluded from authentication scope, so it causes one more redirection to /login, and you get a vicious circle.

Spring Security and SiteMinder integration

I'm trying to integrate SiteMinder with Spring Security. I have a logout button on home page which is supposed to make http get request to backend. I'm trying to invalidate session and redirect back to home page. It's supposed to automatically navigate to log in page which is set up in apache. unfortunate it's not invalidating session or delete cookies.
This is my
WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(ssoHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(ssoAuthProvider())
.logout()
.logoutUrl("/logoutPage")
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID", "GPSESSION")
.invalidateHttpSession(true)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/resources/**")
.permitAll()
.antMatchers( "/**")
.hasRole("ADMIN");
}
AuthenticationController
#RequestMapping(value="/logoutPage", method = RequestMethod.GET)
public void logoutPage (HttpServletRequest request, HttpServletResponse response) throws ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
HttpSession session = request.getSession(false);
request.logout();
}
SSOAuthenticationProvider
public class SSOAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSSOAuthenticationProvider.class);
public static final Map<UserAuthority, UserAuthority> roleMap = new HashMap<>();
static {
roleMap.put(UserAuthority.ADMIN, UserAuthority.ADMIN);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDetailsBean user = (UserDetailsBean) authentication.getPrincipal();
LOGGER.info("Inside WebSSOAuthenticationProvider authenticate method");
if(isValidRoles(user.getRoles())){
return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(),
getAuthoritiesByRoles( user.getRoles()) );
}
throw new BadCredentialsException(user.getFirstName() + " has not valid roles");
}
private boolean isValidRoles(Set<UserAuthority> roles) {
return roles != null && roles.stream().filter(roleMap::containsKey).findAny().isPresent();
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
private List<GrantedAuthority> getAuthoritiesByRoles(Set<UserAuthority> roles) {
List<GrantedAuthority> authorities = roles.stream().map(v -> v.name()).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
SSOAuthenticationFilter
public class SSOAuthenticationFilter extends RequestHeaderAuthenticationFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SSOAuthenticationFilter.class);
#Value("${project.ui.test.mode:false}")
private boolean guiTestMode;
private String FIRST_NAME = "sso-givenname";
private String LAST_NAME = "sso_surname";
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
LOGGER.debug("Working in the test mode for logging");
Enumeration<String> names = request.getHeaderNames();
LOGGER.info("Going to print all http headers");
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = request.getHeader(name);
LOGGER.info("name: " + name + ", value: " + value);
}
UserDetailsBean user = new UserDetailsBean();
if(isTestMode()){
user.setFirstName("Mock");
user.setLastName("User");
user.setRoles(new HashSet<UserAuthority>(Arrays.asList(UserAuthority.ROLE_P_AND_S)));
request.getSession().setAttribute("user" , user);
return user;
}
user.setFirstName(request.getHeader(FIRST_NAME));
user.setLastName(request.getHeader(LAST_NAME));
user.setRoles(new HashSet<UserAuthority>(Arrays.asList(UserAuthority.ROLE_P_AND_S)));
request.getSession().setAttribute("user" , user);
return user;
}
private boolean isTestMode() {
return guiTestMode;
}
}

Spring security switch to Ldap authentication and database authorities

I implemented database authentication for my web page and web service.
It work well for both, now I have to add Ldap authentication.
I have to authenticate through remote Ldap server (using username and password) and if the user exists I have to use my database for user roles (in my database username is the same username of Ldap).
So I have to switch from my actual code to the Ldap and database authentication as above explained. My code is:
SecurityConfig class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the / and /register path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
// CSRF tokens handling
}
}
MyUserDetailsService class
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class);
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username){
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : User doesn't exist" );
else{
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
}catch(Exception e){
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return null;
}
// Converts com.users.model.User user to
// org.springframework.security.core.userdetails.User
private User buildUserForAuthentication(com.domain.User user, List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
so I have to:
1)access of user from login page for web pages and username and password for web services. This has to be done through Ldap.
2)the username of user needs for database query to authenticate user.
Do you have any idea how I can implement this?
Thanks
UPDATE WITH RIGHT CODE: Following the #M. Deinum advice I create MyAuthoritiesPopulator class instead of MyUserDetailsService and authentication with database and Ldap works:
#Service("myAuthPopulator")
public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyAuthoritiesPopulator.class);
#Transactional(readOnly=true)
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : User doesn't exist into ATS database" );
else{
for(UserRole userRole : user.getUserRole()) {
authorities.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
return authorities;
}
}catch(Exception e){
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return authorities;
}
}
and I changed SecurityConfig as below:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("myAuthPopulator")
LdapAuthoritiesPopulator myAuthPopulator;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url("ldap://127.0.0.1:10389/dc=example,dc=com")
// .managerDn("")
// .managerPassword("")
.and()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.ldapAuthoritiesPopulator(myAuthPopulator);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
//Excluede send file from authentication because it doesn't work with spring authentication
//TODO add java authentication to send method
.antMatchers(HttpMethod.POST, "/client/file").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the "/" and "/register" path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
}
My LDAP development environment created in Apache directory studio
Spring Security already supports LDAP out-of-the-box. It actually has a whole chapter on this.
To use and configure LDAP add the spring-security-ldap dependency and next use the AuthenticationManagerBuilder.ldapAuthentication to configure it. The LdapAuthenticationProviderConfigurer allows you to set the needed things up.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(...)
.port(...)
.managerDn(...)
.managerPassword(...)
.and()
.passwordEncoder(passwordEncoder())
.userSearchBase(...)
.ldapAuthoritiesPopulator(new UserServiceLdapAuthoritiesPopulater(this.userService));
}
Something like that (it should give you at least an idea on what/how to configure things) there are more options but check the javadocs for that. If you cannot use the UserService as is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulator for that.
You need to create a CustomAuthenticationProvider wich implements AuthenticationProvider, and override authenticate method, for example:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
boolean authenticated = false;
/**
* Here implements the LDAP authentication
* and return authenticated for example
*/
if (authenticated) {
String usernameInDB = "";
/**
* Here look for username in your database!
*
*/
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(usernameInDB, password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Then, in your SecurityConfig, you need to override the configure thats use AuthenticationManagerBuilder:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
You can autowire the CustomAuthenticationProvider doing this:
#Autowired
private CustomAuthenticationProvider authenticationProvider;
Doing this, you can override the default authentication behaviour.
I also found this chapter Spring Docu Custom Authenicator and build my own switch between LDAP and my DB users. I can effortlessy switch between login data with set priorities (in my case LDAP wins).
I have configured an LDAP with the yaml configuration files for the LDAP user data which I don't disclose here in detail. This can be easily done with this Spring Docu LDAP Configuration.
I stripped the following example off the clatter such as logger/javadoc etc. to highlight the important parts. The #Order annotation determines the priorities in which the login data is used. The in memory details are hardcoded debug users for dev only purposes.
SecurityWebConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Environment env;
#Inject
private LdapConfiguration ldapConfiguration;
#Inject
private BaseLdapPathContextSource contextSource;
#Inject
private UserDetailsContextMapper userDetailsContextMapper;
#Inject
private DBAuthenticationProvider dbLogin;
#Inject
#Order(10) // the lowest number wins and is used first
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new InMemoryUserDetailsManager(getInMemoryUserDetails()));
}
#Inject
#Order(11) // the lowest number wins and is used first
public void configureLDAP(AuthenticationManagerBuilder auth) throws Exception {
if (ldapConfiguration.isLdapEnabled()) {
auth.ldapAuthentication().userSearchBase(ldapConfiguration.getUserSearchBase())
.userSearchFilter(ldapConfiguration.getUserSearchFilter())
.groupSearchBase(ldapConfiguration.getGroupSearchBase()).contextSource(contextSource)
.userDetailsContextMapper(userDetailsContextMapper);
}
}
#Inject
#Order(12) // the lowest number wins and is used first
public void configureDB(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dbLogin);
}
}
DB Authenticator
#Component
public class DBAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// your code to compare to your DB
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
/**
* #param original <i>mandatory</i> - input to be hashed with SHA256 and HEX encoding
* #return the hashed input
*/
private String sha256(String original) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new AuthException("The processing of your password failed. Contact support.");
}
if (false == Strings.isNullOrEmpty(original)) {
md.update(original.getBytes());
}
byte[] digest = md.digest();
return new String(Hex.encodeHexString(digest));
}
private class AuthException extends AuthenticationException {
public AuthException(final String msg) {
super(msg);
}
}
}
Feel free to ask details. I hope this is useful for someone else :D
For anyone using grails it is much simpler. Simply add this to your config:
grails:
plugin:
springsecurity:
ldap:
authorities:
retrieveDatabaseRoles: true

Categories

Resources