I am making custom token authentication in java spring boot, but it doesn't work. Please help.
This is my SecurityConfigurerAdapter :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private BokiAuthenticationProvider bokiAuthenticationProvider;
#Autowired
private MyCredentialsFilter myCredentialsFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
// request handling
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/users").hasRole("USER")
.antMatchers(HttpMethod.GET, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.POST, "/users").permitAll()
.antMatchers(HttpMethod.PATCH, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.DELETE, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.POST, "/login").permitAll()
;
// disable csrf
http.csrf().disable();
// app session is stateless
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(myCredentialsFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false)
.authenticationProvider(bokiAuthenticationProvider);
}
}
This is my filter. The request comes into the filter first. The token string is in the request header. I make a UsernamePasswordAuthenticationToken object out of it :
#Component
public class CredentialsFilter extends OncePerRequestFilter{
#Autowired
private MyCriptoService myCriptoService;
public CredentialsFilter(){
super();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(request.getRequestURI().contains("login")){
chain.doFilter(request, response);
}else{
String token = request.getHeader("MyTokenHeader");
String username = myCriptoService.getUsernameFromToken(token);
if (username!=null && SecurityContextHolder.getContext().getAuthentication()==null){
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(
username,
myCriptoService.getPasswordFromToken(token),
myCriptoService.getAuthoritiesFromToken(token));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
}
}
And this is my AuthenticationProvider :
#Component
public class BokiAuthenticationProvider implements AuthenticationProvider {
#Autowired
private MyUserRepository myUserRepository;
#Autowired
private MyCriptoService myCryptoService;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
if(username!=null && !"".equals(username)){
MyUserJPA jpa = myUserRepository.findByUsername(username);
if(jpa!=null){
String password = auth.getCredentials().toString();
if(myCryptoService.checkPasswords(password, jpa.getPassword())){
#SuppressWarnings("unchecked")
List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) auth.getAuthorities();
return new UsernamePasswordAuthenticationToken(
jpa.getUsername(),
null,
authorities);
}
throw new MyBadCredentialsException("Passwords is missing or invalid.");
}
throw new MyBadCredentialsException("There is no user with username = "+username);
}
throw new MyBadCredentialsException("You did not provide a username.");
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I did debugging. The filter fires and does the .doFilter(request,response), but the AuthenticationProvider doesn't even start.
What am i doing wrong ?
It turns out that authentication provider was authenticating, but there was a problem with the database. I recreated the database, and now it works.
Also, it is impossible for debugging to enter the authenticate-method in the authentication provider once the program is running. That was why my debug was failing.
The source of my confusion was also that my fiddler was not displaying me the JSON from the GET request, but that was an issue with the Fiddler which i solved.
Now I have tested it in more detail now, and everything is working.
Related
I'm trying to implement my own UsernamePasswordAuthenthicationFilter that authenticates every request from my frontend using firebase auth.
public class FireBaseAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
.
.
//Assigning roles happens here
List<GrantedAuthority> authorities = new ArrayList<>();
if (user.getCustomClaims() != null) {
if (user.getCustomClaims().get("boss").equals("true")) {
authorities.add(new SimpleGrantedAuthority("boss"));
}
if (user.getCustomClaims().get("admin").equals("true")) {
authorities.add(new SimpleGrantedAuthority("admin"));
}
if (user.getCustomClaims().get("office").equals("true")) {
authorities.add(new SimpleGrantedAuthority("office"));
}
if (user.getCustomClaims().get("warehouse").equals("true")) {
authorities.add(new SimpleGrantedAuthority("warehouse"));
}
if (user.getCustomClaims().get("store").equals("true")) {
authorities.add(new SimpleGrantedAuthority("store"));
}
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getUid(), authorities));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user.getEmail(),
user.getUid(),
authorities
);
}
filterChain.doFilter(request, response);
}
}
Then i try and replace the default auth in my security config:
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable().csrf().disable().httpBasic().disable().formLogin().disable()
.addFilter(new FireBaseAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
But for some reason my custom filter is never called during runtime? What am I missing here?
Example :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests()
.antMatchers(HttpMethod.POST, ChallengeConstant.AUTHORIZE_ENDPOINT).permitAll()
.antMatchers(HttpMethod.POST, ChallengeConstant.TOKEN_ENDPOINT).permitAll()
.antMatchers(HttpMethod.GET, "/*").permitAll()
.antMatchers(HttpMethod.GET, "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTFilter(userService, objectMapper), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
If you want to validate token :
#AllArgsConstructor
public class JWTFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final UserService userService;
private final ObjectMapper objectMapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(ChallengeConstant.AUTH_HEADER);
if (token != null) {
LOGGER.info("The request is authenticated. Performing Token validity");
String userName;
try {
userName = JWT.require(Algorithm.HMAC512(ChallengeConstant.DUMMY_SIGN.getBytes()))
.build()
.verify(token.replace(ChallengeConstant.TOKEN_PREFIX, ""))
.getSubject();
} catch (JWTVerificationException ex) {
LOGGER.warn(String.format("Token is not valid. Token: %s", token), ex);
generateErrorResponse(httpServletResponse, ExceptionResponse.UNAUTHORIZED);
return;
}
LOGGER.info("Token is valid for username: {}", userName);
try {
UserEntity userEntity = userService.findUserByName(userName);
List<GrantedAuthority> authList = userEntity.getAuthorizations()
.stream()
.map(authorizationEntity -> new SimpleGrantedAuthority(authorizationEntity.getAuth()))
.collect(Collectors.toList());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userEntity.getUserName(), userEntity.getPassword(), authList));
LOGGER.debug("User has been found by given username: {} with authorities: {}", userName, authList.toString());
} catch (NotFoundException ex) {
LOGGER.warn("User couldn't be found with given username: {}", userName);
generateErrorResponse(httpServletResponse, ExceptionResponse.NOT_FOUND);
return;
}
}
LOGGER.info("The request is NOT authenticated. It will continue to request chain.");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void generateErrorResponse(HttpServletResponse httpServletResponse, ExceptionResponse exceptionResponse) throws IOException {
LOGGER.trace("Generating http error response");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(exceptionResponse.getStatus().value());
ErrorResource errorResource = new ErrorResource(exceptionResponse.getCode(),
exceptionResponse.getMessage());
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(errorResource));
LOGGER.trace("Error response is {}", errorResource.toString());
httpServletResponse.getWriter().flush();
}
I think you can validate in Filter and return error response if it is not valid.
Your custom implementation extends the UsernamePasswordAuthenticationFilter (which in its turn extends the AbstractAuthenticationProcessingFilter). The UsernamePasswordAuthenticationFilter, by default, is used for .formLogin authentication, handling the default AntRequestMatcher "/login". If you use a different protected endpoint, the filter's attemptAuthentication() method never gets action. So, if you want to use a different matcher (a different protected endpoint), you have to override the default AntRequestMatcher. For instance, you can do so within your custom filter constructor, by using something like that:
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/signin", "GET"));
The issue is with the app uses custom JWT authentication filter which extends UsernamePasswordAuthenticationFilter which accepts user credentials and generates a long-lived JWT in return.
The issue seems to be with permitAll() which should bypass custom Authorization filter.However in debug mode I could see call to custom JwtAuthorizationFilter first instead of custom JwtAuthenticationFilter Filter which eventually results with 403 forbidden Access denied response.
Note the .antMatchers(HttpMethod.POST, "/login").permitAll() line. /login endpoint should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.
Below is my code
JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
//
private AuthenticationManager authenticationManager;
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/login");
}
/**
* Trigger when we issue POST request to login / we also need to pass in
* {"username: " username, "password": password} in the request body
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// Grab credentials and map them to login viewmodel
LoginViewModel credentials = null;
try {
credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
} catch (IOException e) {
e.printStackTrace();
}
// Create login token
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
credentials.getUsername(), credentials.getPassword(), new ArrayList<>());
// Authenciate user
Authentication auth = authenticationManager.authenticate(authenticationToken);
return auth;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// Grab principal
UserPrincipal principal = (UserPrincipal) authResult.getPrincipal();
// Create JWT Token
String token = JWT.create().withSubject(principal.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.sign(HMAC512(JwtProperties.SECRET.getBytes()));
// add token in response
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
logger.debug("failed authentication while attempting to access "
+ urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
// Add more descriptive message
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
}
JwtAuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//Read the Authorization header, where the JWT token should be
String header = request.getHeader(JwtProperties.HEADER_STRING);
//If header does not contain BEARER or is null delegate to Spring impl and exit
if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// If header is present, try grab user principal from db and perform authorization
Authentication authentication = getUsernamePasswordAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Continue filter execution
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(HttpServletRequest request){
String token = request.getHeader(JwtProperties.HEADER_STRING)
.replace(JwtProperties.TOKEN_PREFIX, "");
if(token !=null){
//parse the token validate it
String userName = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
.build()
.verify(token)
.getSubject();
// Search in the DB if we find the user by token subject(username)
// If so, then grab user details and create auth token using username, pass, authorities/roles
if(userName != null){
User user = userRepository.findByUsername(userName);
UserPrincipal principal = new UserPrincipal(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, null, principal.getAuthorities());
return authenticationToken;
}
return null;
}
return null;
}
}
SecurityConfiguration.java
Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private UserPrincipalDetailsService userPrincipalDetailsService;
private UserRepository userRepository;
public SecurityConfiguration(UserPrincipalDetailsService userPrincipalDetailsService,
UserRepository userRepository) {
this.userPrincipalDetailsService = userPrincipalDetailsService;
this.userRepository = userRepository;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// remove csrf state in session because in jwt do not need them
.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/api/public/management/*").hasRole("MANAGER").antMatchers("/api/public/admin/*")
.hasRole("ADMIN").anyRequest().authenticated().and()
// add jwt filters (1. authentication, 2. authorization_)
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), this.userRepository));
// configure access rules
}
#Bean
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService((UserDetailsService) this.userPrincipalDetailsService);
return daoAuthenticationProvider;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Request,Response
Can someone suggest whats wrong here..Appreciate your help..Thanks in advance..!!!
It seems that your path is wrong. When you look at your body you can see that the path shows following: /login%0A. This seems that you have an extra character at the end of your URL. Just try to rewrite the URL in Postman.
please consider to use shouldNotFilter method from BasicAuthenticationFilter. It extends OncePerRequestFilter so you can use it in filtering class as below:
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// code here
}
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JpaAuditingConfiguration {
#Bean
#Scope(value= ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AuditorAware<String> auditorProvider() {
Authentication authentication
= SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return () -> Optional.<String>empty();
}
return () -> Optional.of(authentication.getName());
}
public static void main(String[] args) {
SpringApplication.run(JpaAuditingConfiguration.class, args);
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailServiceImpl userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/logout/success")
.and().authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.GET, "/logout/**").permitAll()
.anyRequest().authenticated().and()
// Filter for the api/login requests
.addFilterBefore(new LoginFilter("/login",
authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// Filter for other requests to check JWT in header
.addFilterBefore(new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
Authentication authentication
= AuthenticationService
.getAuthentication((HttpServletRequest)request);
SecurityContext securityContext
= SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
auditorProvider seems to be called only once when application starts. authentication is called before it is set in doFilter. authentication object is always null and doesn't update when it's set later in doFilter. When I update a row in db, createdBy and lastModifiedBy are always null.
My doFilter seems to be called when an HTTP request is processed.
I followed a tutorial to learn spring security, but after troubleshooting for a few hours and followed through many similar tutorial, still can't figure out how to properly set the authentication in sequence so when I update a row, spring will update lastModifiedBy and createdBy automatically.
Your issue is that you are creating an anonymous AuditorAware but you are evaluating the SecurityContextHolder.getContext().getAuthentication() outside the body so whatever at that creation time will be kept inside the body.
#Bean
#Scope(value= ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication
= SecurityContextHolder.getContext().getAuthentication();
return Optional.ofNullable(authentication)
.map(Authentication::getName);
};
}
You could say, you have SCOPE_PROTOTYPE but it is useless if it was autowired into a singleton instance that framework uses.
I have a project which I had enabled jwt for authorization on it. The problem is that whenever I send an empty header request or expired authorization code in the header it doesn't send me the unauthorized error, it shows in the log that the token is not valid but allows the request to continue working. this is my configuration code:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(BCryptVersion.$2Y);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user","/swagger-ui.html","/swagger-ui/**"
,"/v3/api-docs/**").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.logout().logoutSuccessUrl("/authenticate").logoutUrl("/logout").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers(String.valueOf(HttpMethod.OPTIONS), "/**");
// ignore swagger
web.ignoring().mvcMatchers("/swagger-ui.html/**", "/configuration/**", "/swagger-resources/**", "/v2/api-docs");
}
}
and this is my jwt request filter:
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
private JwtTokenUtil jwtTokenUtil;
public JwtRequestFilter(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
}
else if (requestTokenHeader == null){
logger.info("Does not provide Authorization Header");
}
else if (!requestTokenHeader.startsWith("Bearer ")){
logger.warn("JWT Token does not begin with Bearer");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
and finally here is JwtAuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
and this is the log that shows I did not send any token in header, but it allows the request:
any idea that what should I do?
for further information, I should say that this code was working but stopped working after a while and I didn't find any reason because I had no changes in these files for months.
The problem is the misconfiguration at this line
web.ignoring().mvcMatchers(String.valueOf(HttpMethod.OPTIONS), "/**");
It should be
web.ignoring().mvcMatchers(HttpMethod.OPTIONS, "/**");
As you might have noticed now that your configuration is actually ignoring all request paths from Spring Security filters. That is the reason that all unauthorized requests (which you expect) are allowed now.
You are missing addFilterAfter and also update your code as below.
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate","/user","/swagger-ui.html","/swagger-ui/**"
,"/v3/api-docs/**").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class).logout().logoutSuccessUrl("/authenticate").logoutUrl("/logout").permitAll();
}
Please refer https://github.com/techiesantosh/taskmanager-service/blob/develop/src/main/java/com/web/taskmanager/config/TaskConfig.java
I have created a REST API that require a authentication with JWT.
My implementation is very similar with the code found on https://auth0.com/blog/securing-spring-boot-with-jwts/
When I try to return the current user, I always receive a null return.
My code:
Websecurity:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// login
.antMatchers(HttpMethod.POST, "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new JWTLoginFilter(
"/login", authenticationManager(), logService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
JWTAuthenticationFilter:
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest req,
ServletResponse res,
FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)req);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(req, res);
}
}
I don't included all the code of JWT authentication, because JWT is working ok, user access too.
I believe the problem is in the filter or some configuration.
Then, I made a facade to get the current user on a service or controller, with the following code (method 4 on http://www.baeldung.com/get-user-in-spring-security):
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
but this don't worked.
- SecurityContextHolder.getContext() returned org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication.
- SecurityContextHolder.getContext().getAuthentication() returned null object.
Update (and solution):
In my controller, if I use this code:
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
I can get the current user, but, in my service, the exact same code don't work.
But then, I remember that SecurityContext is "lost" on another thread (source: https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html), and my service is async
#Async
public CompletableFuture<Optional<ViewUserDto>> findByLogin(String login) throws InterruptedException {
...
}
So, using the code found here: https://stackoverflow.com/a/40347437/4794469, everything works correctly.
I don't known if this can bring any side effects for my code yet (all unit tests worked)
I have worked in an application that has a similar authorization flow as yours:
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider provider;
#Autowired
private TokenAuthenticationService tokenService;
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(provider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
http.csrf().disable();
http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/users", "/v1/oauth/token").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new OAuthTokenFilter("/v1/oauth/token", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AuthorizationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);
}
}
AbstractAuthenticationProcessingFilter
public class OAuthTokenFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper MAPPER = new ObjectMapper();
private TokenAuthenticationService service;
public OAuthTokenFilter(String url, AuthenticationManager manager, TokenAuthenticationService service) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(manager);
this.service = service;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
Login login = MAPPER.readValue(request.getInputStream(), Login.class);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(login.getUsername(), login, Arrays.asList());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) throws IOException, ServletException {
User credentials = (User) authentication.getPrincipal();
String token = service.jwt(credentials);
String json = MAPPER.writeValueAsString(new AuthorizationToken(token, "Bearer"));
response.addHeader("Content-Type", "application/json");
response.getWriter().write(json);
response.flushBuffer();
}
}
GenericFilterBean
public class AuthorizationFilter extends GenericFilterBean {
private TokenAuthenticationService service;
public AuthorizationFilter(TokenAuthenticationService service) {
this.service = service;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = service.getAuthentication((HttpServletRequest)request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
TokenAuthenticationService
#Service
public class TokenAuthenticationService {
public static final String JWT_SECRET_ENV = "JWT_SECRET";
public static final String ISSUER = "my issuer";
public static final String ROLE_CLAIM = "role";
public static final String THIRDY_PARTY_ID_CLAIM = "thirdy_party_id";
public static final String TOKEN_PREFIX = "Bearer";
public static final String HEADER = "Authorization";
#Autowired
private Environment environment;
public Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER);
String secret = environment.getProperty(JWT_SECRET_ENV);
if (token != null) {
try {
String bearer = token.replace(TOKEN_PREFIX, "").trim();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
DecodedJWT jwt = verifier.verify(bearer);
User user = new User();
user.setId(jwt.getSubject());
user.setThirdPartyId(jwt.getClaim(THIRDY_PARTY_ID_CLAIM).asString());
user.setRole(jwt.getClaim(ROLE_CLAIM).asString());
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
return new UsernamePasswordAuthenticationToken(user, null, authorities);
} catch (Exception e){
e.printStackTrace(System.out);
}
}
return null;
}
}
And then, the controller:
#RestController
public class UserController {
#ResponseBody
#GetMapping("/v1/users/{id}")
#PreAuthorize("hasAuthority('USER')")
public User get(#PathVariable("id") String id, Authentication authentication) {
User user = (User) authentication.getPrincipal();
return user;
}
}
I faced similar issue when i was enabling JWT on my web app.
You need: "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files".
Please download the this package from the below URL and replace US_export_policy.jar, local_policy.jar (\jre\lib\security)
If it is still not working, then you need to replace the above jar files in the location \lib\security
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html