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