Implement Windows AD Authentication, Local DB Authorization with JWT Response Token using Spring Security

  • Check if the user exist in local database user table and if the user exist, then authenticate it to external windows AD server.
  • Upon successful call to login API, a session was created on server and session id was returned in response header.
  • Subsequent call to other APIs were done with session id token.
  1. Add following dependencies to pom.xml file.
  <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
jwt.secret=mysecretkey12345spring.jpa.show-sql = true
spring.jpa.generate-ddl=false
spring.sql.init.mode=never
#DATABASE CONFIGURATION spring.datasource.url=jdbc:postgresql://localhost/pgdb
spring.datasource.username=pguser
spring.datasource.password=pguser
spring.datasource.driver-class-name=org.postgresql.Driver
@Entity
public class Myuser {
@Id
private String username;
private String authority;
// constructor, getter & setter
}
@Repository
public interface MyuserRepo extends JpaRepository<Myuser, String> {}
@Service
@Transactional
public class MyuserService {

private final MyuserRepo myuserRepo;
public MyuserService(MyuserRepo myuserRepo) {
this.myuserRepo = myuserRepo;
}
public boolean findOne(String username) {
Optional<Myuser> myuser = myuserRepo.findById(username);
return myuser.isPresent();
}

public Myuser loadUserByUsername(String username) throws UsernameNotFoundException {
return myuserRepo.findById(username).get();
}
public List<Myuser> findAll() {
return myuserRepo.findAll();
}
}
public class AuthenticationRequest implements Serializable {
private String username;
private String password;
//constructors and getter setter methods
}public class AuthenticationResponse implements Serializable {
private final String jwt;
//constructors and getter setter methods
}
@Service
public class JwtUtil {
@Value("${jwt.secret}") // read key from application.properties file
private String SECRET_KEY;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(Myuser userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, Myuser userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}

}
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyuserService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");String username = null;
String jwt = null;
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null & SecurityContextHolder.getContext().getAuthentication() == null) {
Myuser userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {

// get user authority from local db
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_".concat(userDetails.getAuthority())));

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, grantedAuthorities);

usernamePasswordAuthenticationToken
.setDetails( new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable
{
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

private final MyuserService myuserService;

public CustomAuthenticationProvider(MyuserService myuserService) {
super();
this.myuserService = myuserService;
}
@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider(){

ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(
"example.examplegroup.co.in","ldap://192.168.1.25");

// to parse AD failed credentails error message due to account - expiry,lock, credentialis - expiry,lock
activeDirectoryLdapAuthenticationProvider.setConvertSubErrorCodesToExceptions(true);
return activeDirectoryLdapAuthenticationProvider;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

String username = authentication.getName();
boolean ifPresent = myuserService.findOne(username);

if(ifPresent) {
return activeDirectoryLdapAuthenticationProvider().authenticate(authentication);
}
else throw new UsernameNotFoundException("User not found.");
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private JwtRequestFilter jwtRequestFilter;
private CustomAuthenticationProvider customAuthenticationProvider;
public WebSecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtRequestFilter jwtRequestFilter,
CustomAuthenticationProvider customAuthenticationProvider) {
super();
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtRequestFilter = jwtRequestFilter;
this.customAuthenticationProvider = customAuthenticationProvider;
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}


@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.headers().frameOptions().deny()
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.anyRequest().authenticated()
.and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

http.addFilterBefore(jwtRequestFilter,UsernamePasswordAuthenticationFilter.class);

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

}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final long MAX_AGE_SECS = 3600;@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://myserver2:3000", "http://myserver3:3000")
.allowedMethods("GET", "POST")
.maxAge(MAX_AGE_SECS)
.allowCredentials(true);
}
}
  • login api that performs authentication and responds with jwt token if successful.
  • API thats check returns “hello world” if the associated jwt token in payload is valid.
@RestController
@RequestMapping("/api")
public class AuthenticationResource {

private final AuthenticationManager authenticationManager;
private final JwtUtil jwtTokenUtil;
private final MyuserService myuserService;

public AuthenticationResource(AuthenticationManager authenticationManager, JwtUtil jwtTokenUtil,
MyuserService myuserService) {
super();
this.authenticationManager = authenticationManager;
this.jwtTokenUtil = jwtTokenUtil;
this.myuserService = myuserService;
}
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
}
catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final Myuser userDetails = myuserService.loadUserByUsername(authenticationRequest.getUsername());final String jwt = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
@GetMapping("/hello")
public String sayHello(){
return "Hello World";
}
  • Make authentication call as follows
const onLogin= () => {onst AUTH_TOKEN_KEY = 'authenticationToken';
const loginUrl = "http://myserver1:8080/api/authenticate";
axios.post(loginUrl, { username, password })
.then(res => {
const bearerToken = res.data.jwt;
if (bearerToken) {
localStorage.setItem(AUTH_TOKEN_KEY, bearerToken);
setBearerId(bearerToken);
setIsAuthenticated(true);
}
})
.catch(err => console.log(err.message));
}
  • Make endpoint api call with bearer id as follows
const endpointUrl = 'http://myserver1:8080/api/hello';const authHeader = "Bearer "+ localStorage.getItem('authenticationToken');axios
.get(endpointUrl,{ headers: { 'Authorization': authHeader } } )
.then(res => console.log(res.data))
.catch(err => console.log(err.message));
}

--

--

--

System Administrator and Full stack web developer.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

jQuery Is Better Than React

Build react native shimmering component

Advanced Data Manipulation in Javascript

WebRTC for modern browser based communication

ES6: Understanding Default & Rest parameters, Spread Operator and its usages

Empty string comparison in JavaScript

The Most Important Terms Every Web Developer Should Know

Javascript Closures, Partial Functions, and Currying

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Himanshu Pratap

Himanshu Pratap

System Administrator and Full stack web developer.

More from Medium

Custom Pagination with Spring Data and MongoDb

Making Readable Code With Dependency Injection and Jakarta CDI

Build Your First Spring Boot Project!

Sample Apps: Spring data MongoDB and JSF Integration tutorial (PART 5)