Migration to Ldap based authentication from Inmemory authentication - spring

I have to implement a LDAP based authentication and return a JWT token in response which will be used in subsequent request.
I followed this guide InMemory Authentication with JWT to implement InMemory based Authentication. I know and tried standalone code for LDAP authentication and it's working.
While integrating ldap authentication in the code explained in the above link's example,I am getting stuck and not able to move forward.
I am not getting, How to define loadByUsername method of UserDetailsService class in case of ldap authentication.
Looking for some direction/guidance on this to proceed.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
#Autowired
private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
#Autowired
private Environment env;
#Value("${jwt.get.token.uri}")
private String authenticationPath;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new LdapAuthenticationProvider(env)).eraseCredentials(false);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity
.headers()
.frameOptions().sameOrigin() //H2 Console Needs this setting
.cacheControl(); //disable caching
}
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers(
HttpMethod.POST,
authenticationPath
)
.antMatchers(HttpMethod.OPTIONS, "/**")
.and()
.ignoring()
.antMatchers(
HttpMethod.GET,
"/" //Other Stuff You want to Ignore
)
.and()
.ignoring()
.antMatchers("/h2-console/**/**");//Should not be in Production!
}
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
#Component
public class LdapAuthenticationProvider implements AuthenticationProvider
{
private Environment environment;
public LdapAuthenticationProvider(Environment environment) {
this.environment = environment;
}
private LdapContextSource contextSource;
private LdapTemplate ldapTemplate;
private void initContext(Authentication authentication)
{ contextSource = new LdapContextSource();
contextSource.setUrl(environment.getProperty("ldap.server.url"));
//contextSource.setAnonymousReadOnly(true);
contextSource.setUserDn("domain\\uid");
contextSource.setBase("DC=global,DC=comp,DC=org");
contextSource.setPassword("pwd");
contextSource.afterPropertiesSet();
ldapTemplate = new LdapTemplate(contextSource);
ldapTemplate.setIgnorePartialResultException(true);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
initContext(authentication);
Filter filter = new EqualsFilter("sAMAccountName", authentication.getName());
Boolean authenticate = ldapTemplate.authenticate(LdapUtils.emptyLdapName(), filter.encode(), authentication.getCredentials().toString());
if (authenticate)
{
UserDetails userDetails = new User(authentication.getName(), authentication.getCredentials().toString()
, new ArrayList<>());
Authentication auth = new UsernamePasswordAuthenticationToken(userDetails,
authentication.getCredentials().toString(), new ArrayList<>());
return auth;
}
else
{
return null;
}
}
#Override
public boolean supports(Class<?> authentication)
{
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
#Component
public class JwtTokenAuthorizationOncePerRequestFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private UserDetailsService JwtLdapUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.http.request.header}")
private String tokenHeader;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
logger.debug("Authentication Request For '{}'", request.getRequestURL());
final String requestTokenHeader = request.getHeader(this.tokenHeader);
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
logger.error("JWT_TOKEN_UNABLE_TO_GET_USERNAME", e);
} catch (ExpiredJwtException e) {
logger.warn("JWT_TOKEN_EXPIRED", e);
}
} else {
logger.warn("JWT_TOKEN_DOES_NOT_START_WITH_BEARER_STRING");
}
logger.debug("JWT_TOKEN_USERNAME_VALUE '{}'", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.JwtLdapUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Above are some code files (modified for ldap) in the application. Complete code (base of my changes - InMemory based auth+Jwt) is available in the link mentioned above.
Thanks,

Related

Is there a different manner to do the authentication with the new spring security using jwt?

I try to do the security of my project adapting the baeldung documentation and the previous code, and I want to know the normal method of this. I have the following configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception{
return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/create", "/api/login", "/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
then I have this token provider
#Component
public class JtwTokenProvider {
#Value("${app.jwt-secret}")
private String jwtSecret;
#Value("${app.jwt-expiration-milliseconds}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
String token = Jwts.builder().setSubject(username).setIssuedAt(new Date())
.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
return token;
}
public String getUsernameOfJwt(String token){
Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
public boolean validateToken(String token){
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
}catch (SignatureException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT signature");
}catch (MalformedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT token");
}catch (ExpiredJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Expired JWT token");
}catch (UnsupportedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported JWT token");
}catch (IllegalArgumentException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "JWT claims string is empty");
}
}
}
The token filter is the same and this manner is okay? What others exist?
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JtwTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//Get JWT from request
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
// Get username from token
String username = tokenProvider.getUsernameOfJwt(token);
// Load user associated with the token
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//Set the authentication in the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.replace("Bearer ", "");
}
return null;
}
}
Then I used the userdetails service
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(
() -> new NotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), getAuthority(user.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthority(Set<Role> roles){
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Finally here is my authentication, this is safe? what is the standart manner of do the same?
#Service
public class LoginServiceImpl implements LoginService{
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JtwTokenProvider jwtTokenProvider;
#Override
public JwtAuthResponse login(LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
return new JwtAuthResponse(jwt);
}
}
I was waiting your responses thanks :D

Invalid cors request in spring boot

I am running a spring boot application in conjunction with graphql and jwt token. Currently when I am trying to hit one of the endpoints I am getting 'Invalid Cors Request'. Below is the code of config and filter file.Config file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userService);
}
#Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable().cors().disable().authorizeRequests().antMatchers("/**", "/graphql", "/graphiql", "/graphql/**", "/graphiql/**")
.permitAll().anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Filter file:
#Component
#Log4j2
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserService userService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if(authorizationHeader==null)
throw new ServletException();
String token = null;
String userName = null;
if (authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
userName = jwtUtil.extractUsername(token);
}
else
throw new ServletException();
if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(userName);
if (jwtUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else
throw new ServletException();
}
else
throw new ServletException();
filterChain.doFilter(request, response);
}
}
The rest is a simple graphql project with kickstart implementation of graphql. I only have a couple of queries and I am trying to hit it via Altair extension. Please let me know if any more information is required. Thanks in advance.

Add additional user requirements in spring security login and handle various exceptions

I am new to Spring security, I have implemented a basic user login functionality for my app using JWT. Aside from checking for username and password at login I would like to add other parameters such as a "account is verified" boolean condition but I am not sure where to add this requirement. Additionally, I need to return a 403 forbidden response status message if the "account is verified" condition is false and return a different response status message if the username password combination isn't found at all. Here Is the code I currently have which correctly handles the login of an existing user (without checking for the "account is verified" condition) and always throws a 401 when the user is found. Any feedback would be helpful.
WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ApplicationUserDetailsService applicationUserDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(ApplicationUserDetailsService userDetailsService) {
this.applicationUserDetailsService = userDetailsService;
this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return this.bCryptPasswordEncoder;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserDetailsService
public class ApplicationUserDetailsService implements UserDetailsService {
private final ApplicationUserRepository applicationUserRepository;
public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String nickname)
throws UsernameNotFoundException, UserIsNotActiveException {
Optional<ApplicationUser> applicationUser =
applicationUserRepository.findByNickname(nickname);
if (!applicationUser.isPresent()) {
throw new UsernameNotFoundException(nickname);
}
return new User(
applicationUser.get().getNickname(),
applicationUser.get().getPassword(),
emptyList());
}
}
AuthenticationFilter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
ApplicationUser applicationUser =
new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
applicationUser.getNickname(),
applicationUser.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
Key key = Keys.hmacShaKeyFor(KEY.getBytes());
Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
String token =
Jwts.builder()
.setClaims(claims)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(exp)
.compact();
res.addHeader("token", token);
}
}
AuthorizationFilter
public AuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(HEADER_NAME);
if (header == null) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = authenticate(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request) {
String token = request.getHeader(HEADER_NAME);
if (token != null) {
Jws<Claims> user =
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
.build()
.parseClaimsJws(token);
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
} else {
return null;
}
}
return null;
}
ApplicationUser
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique = true)
String email;
#Column(unique = true)
String nickname;
String biography;
String password; // Hashed
#Builder.Default boolean isActive = false;
}
The interface UserDetails (that is returned by the UserDetailsService) has some utility methods that can help you with it.
While the account is not activated, you can return false from the UserDetails#isEnabled method, or maybe you can use UserDetails#isAccountNonLocked as well.
Those methods will then be automatically validated on the AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks class.
After the user goes through the activation flow, you can change the property to true and it will allow the user to authenticate.
Tip: add the logging.level.org.springframework.security=TRACE to your application.properties to help to debug.

Swagger UI being blocked by Spring Security

I am trying to implement spring security with JWT token, I am trying to achieve authentication with method level authorization
My configuration looks like this
SwaggerConfig.java
#Configuration
#PropertySource({"classpath:application.properties"})
#EnableSwagger2
#EnableWebMvc
public class SwaggerConfiguration implements WebMvcConfigurer {
#Autowired
private Environment env;
#Value("${swagger.enable:false}")
private Boolean isEnabled;
#Bean
public Docket swaggerBean() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(isEnabled)
.select()
.apis(RequestHandlerSelectors.basePackage("com.my.packageapi.v1"))
.paths(PathSelectors.any())
.build()
.apiInfo(getApiInfo())
.tags(new Tag(env.getProperty("swagger.display.project.name"), env.getProperty("swagger.display.project.description")));
}
private ApiInfo getApiInfo() {
return new ApiInfoBuilder()
.title(env.getProperty("swagger.display.page.title"))
.description(env.getProperty("swagger.display.module.description"))
.version(env.getProperty("swagger.display.version"))
.build();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/media/**", "/assets/**", "/static/**", "/images/**", "/css/**", "/js/**")
.addResourceLocations("classpath:/assets/", "classpath:/static/media/", "classpath:/static/images/",
"classpath:/static/css/", "classpath:/static/js/", "classpath:js/");
registry.addResourceHandler("/dist/**").addResourceLocations("/dist/");
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
//registry.addResourceHandler(contextPath+"/dist/**").addResourceLocations(contextPath+"/dist/");
//registry.addResourceHandler(contextPath+"/static/**").addResourceLocations(contextPath+"/static/");
}
}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
//#EnableGlobalMethodSecurity(securedEnabled = true)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Qualifier("userService")
#Autowired
private UserDetailsService userDetailsService;
#Qualifier("ApplicationAuthenticationManager")
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtAuthenticationFilter authenticationFilter;
#Autowired
private PasswordEncoder encoder;
#Override
public AuthenticationManager authenticationManagerBean() {
return authenticationManager;
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/swagger**");
http.cors().and().csrf().disable().
authorizeRequests()
.antMatchers(
"/token/**",
"/configuration/ui",
"/swagger-resources/*",
"/configuration/security",
"/webjars/*",
"/swagger-ui*",
"/favicon*").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/*",
"/configuration/security",
"/swagger-ui*",
"/swagger-ui.html/*",
"/webjars/*");
}
JWTAthenticationFilter.java
#Configuration
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Qualifier("userService")
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private TokenProvider jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX, "");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occurred during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch (SignatureException e) {
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
//UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
System.err.println("Filtering " + req.getContextPath() + " " + req.getRequestURL());
chain.doFilter(req, res);
}
}
JWTAthenticationEntryPoint.java
#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");
}
I have followed it from here
Already tried many of solution, listing few below
Swagger UI empty and gives 403
If you spot any other improvements, please feel free to drop your comments. Every bit of help is appreciated.
In the ant matchers part add .antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**").permitAll()

Spring Security and Token Auth for API

I've been scouring the web looking at a lot of different ways to implement token based authentication using Spring & Spring Security (SS). I'm not really wanting to go the full on Oauth route so I've been trying to do something and keep things pretty simple.
What I want is to pass a username/password to SS's built in mechanism and on success, generate a token that I pass back to the user. The user then makes all future requests with the token in a custom header. The token will expire after som length of time. I am aware this is kind of what Oauth does but again, don't want to use it.
So I've got something kind of working. I can login with username/password and get the token back. I can then make requests with the token successfully. What isn't working are authorities. Here's what I'm doing...
Custom Auth Failure Handler that simply returns HttpServletResponse.SC_UNAUTHORIZED
Custom Success Handler that simply returns HttpServletResponse.SC_OK and the token
Custom Auth Entry Point that just response with HttpServletResponse.SC_UNAUTHORIZED
Now, I also have a custom UserDetails and UserDetailsService.
public class MyUserDetails implements UserDetails {
private User user; // this is my own User object
private List<GrantedAuthority> authorities;
public MyUserDetails(User user, List<GrantedAuthority> authorities) {
this.user = user;
this.authorities = authorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
#Service
public class MyUserDetailsService implements UserDetailsService {
private final UserService userService;
#Autowired
public MyUserDetailsService(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
List<GrantedAuthority> authorities = new ArrayList<>();
// for now, just add something
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new MyUserDetails(user, authorities);
}
}
In order to look in the header for the token and tell spring all is well, I created a AuthTokenFilter...
public class AuthTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader("X-TOKEN-AUTH");
String username = null;
if (authToken != null) {
username = Jwts.parser()
.setSigningKey("1234")
.parseClaimsJws(authToken)
.getBody()
.getSubject();
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// TODO: validate token
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
And this is how I've configured web security:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
private AuthSuccessHandler authSuccessHandler;
#Autowired
private AuthFailureHandler authFailureHandler;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(new ShaPasswordEncoder());
return authenticationProvider;
}
#Bean
public AuthTokenFilter authenticationTokenFilterBean() throws Exception {
AuthTokenFilter authenticationTokenFilter = new AuthTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout()
.permitAll()
.and()
.sessionManagement()
.maximumSessions(1);
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests().anyRequest().authenticated();
}
}
Everything seems to work accept SS isn't limiting access at all. If the token is there SS just lets everything pass.
Well, after much trial and error it was as simple as adding the following to my SpringSecurityConfig
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
Kind of surprised that I didn't run into this sooner. Not sure if this is something somewhat new or what.

Resources