I have the following application.
SpringMainApplication.java
@SpringBootApplication
public class SpringMainApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringMainApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringMainApplication.class, args);
}
}
@RestController
class MainController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@Value("${token.expiration.time}")
private int tokenExpirationTime;
@RequestMapping(value = "/service1/access-token", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
}
catch (BadCredentialsException e) {
throw new Exception("Incorrect password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("expiration", String.valueOf(tokenExpirationTime / 1000));
return ResponseEntity.ok().headers(responseHeaders).body(new AuthenticationResponse(jwt));
}
@RequestMapping(value = "/service1/test", consumes = "application/json", method = RequestMethod.GET)
public ResponseEntity<?> checkServerStatus() throws Exception {
// SOME CODE
}
@RequestMapping(value = "/service2/access-token", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
}
catch (BadCredentialsException e) {
throw new Exception("Incorrect password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("expiration", String.valueOf(tokenExpirationTime / 1000));
return ResponseEntity.ok().headers(responseHeaders).body(new AuthenticationResponse(jwt));
}
@RequestMapping(value = "/service2/test", consumes = "application/json", method = RequestMethod.GET)
public ResponseEntity<?> checkServerStatus() throws Exception {
// SOME CODE
}
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/**/service1/access-token").permitAll().
anyRequest().authenticated().and().
exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
MyUserDetailsService.java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Value("${username1}")
private String username;
@Value("${password1}")
private String password;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username != null && username.equals(username1)) {
return new User(username, password1, new ArrayList<>());
} else {
throw new UsernameNotFoundException("Username not found: " + username);
}
}
}
JwtRequestFilter.java
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
StringBuilder sb = new StringBuilder();
try {
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
} catch (UnsupportedJwtException e) {
sb = buildResponseBody(request, e.getMessage());
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtUtil.java
@Service
public class JwtUtil {
@Value("${token.expiration.time}")
private String tokenExpirationTime;
private String SECRET_KEY = "some_secret";
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(UserDetails 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() + Integer.parseInt(tokenExpirationTime)))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
The issue I am having is that if I generate a token by calling service1/access-token
, I can still call service2/test
using the jwt, and vice versa.
How can I restrict the user from accessing service2 with a valid service1 token?
I believe I might have to create a second WebSecurityConfig
class to handle service2, but I am not sure how it is done because the app won't compile when I try to add one.
AuthenticationManager
seems a bit, I dunno, faulty to me. – Heffner