Spring Security – Authentication

First you need to the spring-boot-starter-security in you pox.xml

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Configure Spring security using java. This configuration is for APIs.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomAuthenticationProvider customAuthenticationProvider;
	
	@Autowired
	private CustomLogoutHandler customLogoutHandler;
	
	@Autowired
	private CustomLogoutSuccessHandler customLogoutSuccessHandler;
	
	@Autowired
	private CustomAcccessDeniedHandler customAcccessDeniedHandler;
	
	@Bean
	public CustomLoginFilter customUsernamePassworAuthenticationFilter() throws Exception {
		return new CustomLoginFilter(PathConstantUtil.LOGIN_URL,authenticationManagerBean());
	}
	
	@Bean
	public CustomAuthenticationFilter customAuthenticationFilter() {
		return new CustomAuthenticationFilter();
	}
	
	@Bean
	public RegistrationBean jwtAuthFilterRegister(CustomAuthenticationFilter customAuthenticationFilter) {
		FilterRegistrationBean<CustomAuthenticationFilter> registrationBean = new FilterRegistrationBean<CustomAuthenticationFilter>(
				customAuthenticationFilter);
		registrationBean.setEnabled(false);
		return registrationBean;
	}
	
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// rest call rules
		http
			.cors().and().csrf().disable()
			.authorizeRequests()
				.antMatchers(PathConstantUtil.PING_URL).permitAll()
				.antMatchers(PathConstantUtil.LOGIN_URL).permitAll()
				.antMatchers(PathConstantUtil.SIGNUP_URL).permitAll()
				.anyRequest().permitAll();
					
		// logout
		http.logout()
			.logoutRequestMatcher(new AntPathRequestMatcher(PathConstantUtil.LOGOUT_URL))
			.addLogoutHandler(customLogoutHandler)
			.logoutSuccessHandler(customLogoutSuccessHandler);
		
		// filter
		http.addFilterBefore(customUsernamePassworAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
		
		http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
		
		// stateless
		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
		
		// handler access denied calls
		http.exceptionHandling().accessDeniedHandler(customAcccessDeniedHandler);
	}

	@Override
	protected void configure(AuthenticationManagerBuilder builder) throws Exception {
		builder.authenticationProvider(customAuthenticationProvider);
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring()
			.antMatchers(PathConstantUtil.SIGNUP_URL)
			.antMatchers(PathConstantUtil.LOGIN_URL)
			.antMatchers(PathConstantUtil.PING_URL)
			.antMatchers(PathConstantUtil.AUTH_TOKEN_URL)
			.antMatchers(PathConstantUtil.SWAGGER_DOC_URLS)
			.antMatchers("/actuator/**");
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		return encoder;
	}

	@Bean
	public MethodInvokingFactoryBean methodInvokingFactoryBean() {
	    MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
	    methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
	    methodInvokingFactoryBean.setTargetMethod("setStrategyName");
	    methodInvokingFactoryBean.setArguments(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
	    return methodInvokingFactoryBean;
	}
	
}

Here we configure our AuthenticationManager, PasswordEncoder, and web security for http routes.

Configure your Authentication Provider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
	private Logger log = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private UserService userService;

	/**
	 * Authenticate user by credentials
	 * 
	 * @author fkaveinga
	 * @return Authentication
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		log.info("authenticate(...)");
		
		String email = authentication.getPrincipal().toString();
		String password = authentication.getCredentials().toString();
		
		log.info("email: {}", email);
		log.info("password: {}", password);
		
		Map<String, String> details = (Map) authentication.getDetails();
		log.debug("details: {}", ObjectUtils.toJson(details));
		
		return loginWithPassword(email, password);
	}

	private Authentication loginWithPassword(String email, String password) {
		log.info("loginWithPassword({})", email);
		Optional<User> optUser = userService.findByEmail(email);

		if (!optUser.isPresent()) {
			log.info("user not found");
			throw new UsernameNotFoundException("Username or password is invalid");
		}
		
		log.info("user found for {}", email);
		
		User user = optUser.get();
		
		log.info("user: {}", ObjectUtils.toJson(user));
		
		if (user.getPassword() == null || !PasswordUtils.verify(password, user.getPassword())) {
			log.info("login credentials not matched");
			throw new BadCredentialsException("Username or password is invalid");
		}

		return new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), generateAuthorities(user.getRoles()));
	}
	

	/**
	 * Get Authorities for User
	 * 
	 * @param user
	 * @return List<GrantedAuthority>
	 */
	private List<GrantedAuthority> generateAuthorities(Set<Role> roles) {
		List<GrantedAuthority> authorities = new ArrayList<>();

		if (roles.isEmpty()) {
			throw new InsufficientAuthenticationException("No role");
		} else {
			for (Role role : roles) {
				authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getAuthority().toUpperCase()));
			}
		}
		return authorities;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return authentication.equals(UsernamePasswordAuthenticationToken.class);
	}

}

Configure your Login Filter to handle login

public class CustomLoginFilter extends AbstractAuthenticationProcessingFilter {

	private Logger log = LoggerFactory.getLogger(this.getClass());
	
	private Map<String,String> authenticationDetails = new HashMap<>();

	@Autowired
	private UserService userService;
	
	public CustomLoginFilter(String loginUrl, AuthenticationManager authManager) {
		super(new AntPathRequestMatcher(loginUrl));
		setAuthenticationManager(authManager);
	}

	
	/**
	 * Attemp Authentication process. Pass username and password to authentication provider
	 * @author fkaveinga
	 * @return Authentication
	 */
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
		
		String authorizationHeader = request.getHeader("authorization");
		log.info("Login Authorization Header: {}",authorizationHeader);
		
		if(authorizationHeader==null) {
			throw new InsufficientAuthenticationException("Authorization Header is null");
		}
		
		String email = getUsername(authorizationHeader);
		String password = getPassword(authorizationHeader);
		log.debug("email: {}",email);
		log.debug("password: {}",password);
		
		if(email == null || email.isEmpty()) {
			log.info("username is null");
			throw new InsufficientAuthenticationException("Username is null");
		}
		
		if(password == null || password.isEmpty()) {
			log.info("password is null");
			throw new InsufficientAuthenticationException("Password is null");
		}
		
		authenticationDetails.put("test", "good");
		
		return authenticateWithPassword(email, password);
		
	}
	
	private Authentication authenticateWithPassword(String email, String password) {
		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(email, password);
		usernamePasswordAuthenticationToken.setDetails(authenticationDetails);
		return getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
	}
	

	/**
	 * Write response when request was successful.
	 * @author fkaveinga
	 * @return HttpServletResponse
	 */
	@Override
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
		log.debug("successfulAuthentication(...)");
		String clientIpAddress = HttpUtils.getRequestIP(request);
		String clientUserAgent = HttpUtils.getRequestUserAgent(request);
		
		String email = authResult.getPrincipal().toString();
		
		User user = this.userService.getByEmail(email);
		
		JwtPayload jwtpayload = new JwtPayload(user, RandomGeneratorUtils.getUuid());
		jwtpayload.setDeviceId(clientUserAgent);
		
		String jwtToken = JwtTokenUtils.generateToken(jwtpayload);
		
		SessionDTO sessionDto = new SessionDTO();
		sessionDto.setEmail(email);
		sessionDto.setName(user.getName());
		sessionDto.setUserUid(user.getUid());
		sessionDto.setToken(jwtToken);
		
		response.setStatus(HttpStatus.OK.value());
		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		
		ObjectUtils.getObjectMapper().writeValue(response.getWriter(),sessionDto);
	}

	/**
	 * Write response when request was unsuccessful.
	 * @author fkaveinga
	 * @return HttpServletResponse
	 */
	@Override
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
		log.debug("unsuccessfulAuthentication(...)");
		
		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		response.setStatus(HttpStatus.BAD_REQUEST.value());
		
		String message = failed.getLocalizedMessage();
		log.debug("Error message: {}",message);

		response.setStatus(HttpStatus.BAD_REQUEST.value());
		
		ObjectNode result = ObjectUtils.getObjectNode();
		
		result.put("status", "invalid email or password");
		
		ObjectUtils.getObjectMapper().writeValue(response.getWriter(), result);
	}
	
	/**
	 * Parse token for username
	 * @param authorizationHeader
	 * @return String username
	 */
	private String getUsername(String authorizationHeader) {
		log.debug("getUsername(..)");
		String username = null;
		try {
			String usernamePasswordToken = StringUtils.substringAfter(authorizationHeader, " ").trim();
			//log.info("usernamePasswordToken: {}",usernamePasswordToken);
			String rawToken = this.decodeBase64Token(usernamePasswordToken);
			log.debug("rawToken: {}",rawToken);
			username  = StringUtils.substringBefore(rawToken, ":");
			log.debug("username: {}",username);
			return username;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return username;
	}
	
	/**
	 * Parse token for password
	 * @param authorizationHeader
	 * @return String password
	 */
	private String getPassword(String authorizationHeader) {
		log.debug("getPassword(..)");
		String password = null;
		try {
			String usernamePasswordToken = StringUtils.substringAfter(authorizationHeader, " ").trim();

			String rawToken = this.decodeBase64Token(usernamePasswordToken);
			log.debug("rawToken: {}",rawToken);
			password  = StringUtils.substringAfter(rawToken, ":");
			log.debug("username: {}",password);
			return password;
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return password;
		
	}
	
	/**
	 * Parse for access token
	 * @param authorizationHeader
	 * @return String access token
	 */
	private String getAccessToken(String authorizationHeader) {
		log.debug("getAccessToken(..)");
		String bearerToken = null;
		try {
			bearerToken = StringUtils.substringAfter(authorizationHeader, " ").trim();
			log.info("bearerToken: {}",bearerToken);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bearerToken;
	}
	
	
	/**
	 * Decode authentication token
	 * @param usernamePasswordToken
	 * @return String
	 */
	private String decodeBase64Token(String usernamePasswordToken) {
		byte[] decodedBytes = Base64.getDecoder().decode(usernamePasswordToken);
		return new String(decodedBytes);
	}

}

Source code in Github




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *