I try to authenticate throught a /authenticate api to generate a token but it always gives me access denied even when username and password are correct.the problem was int the code of csrf().disabled().
I tried to remove CSRF().disabled() from the configure method it does remove the problem of access denied but it does not give me anything as a response, it does not generate the JWT(nothing is sent as a response).
Here is my controller:
#RestController
#RequestMapping("/api")
public class SecurityController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private MyUserDetailService userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#GetMapping("/test")
private String test() {
return "Test";
}
#GetMapping("/hello")
private String hello() {
return "hello";
}
#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 UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
securityConfig file:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailService myUserDetailService;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService);
}
#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("/authenticate").permitAll().anyRequest()
.authenticated();
}
}
your endpoints name is /api/authenticate.
so
.antMatchers("/authenticate")
wont work, you need to match on the correct url.
Related
I'm currently building a standard JwtAuthorizationFilter. I extend the OncePerRequestFilter class for this. Furthermore I have a JwtUtils class, which contains all JWT methods. For example, one method validates the JWT bearer token. However, I keep getting the error that this method (and all others) cannot be invoked because this.jwtUtils is null.
So bassicly I am trying to autowire the JwtUtils classe. But Spring is not giving any instance. Instead it is giving null
Thats my error message
Cannot invoke ... jwt.JwtUtils.validateJwtToken(String)" because "this.jwtUtils" is null
JwtAuthorizationFilter class (the error is throwing here)
#Slf4j
public class JwtAuthorizationFilter extends OncePerRequestFilter {
#Autowired private JwtUtils jwtUtils;
#Autowired private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = JwtUtils.resolveToken(request);
System.out.println("before if " + jwt);
if (jwtUtils.validateJwtToken(jwt)) { // ERROR !
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
JwtAuthorizationFilter.log.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
}
JwtUtils class
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
#Slf4j
#Component
public class JwtUtils {
#Value("${ggg.app.jwtSecret}")
private String jwtSecret;
#Value("${ggg.app.jwtExpirationMs}")
private long jwtExpirationMs;
#Value("${ggg.app.jwtRefreshExpirationMs}")
private long jwtRefreshExpirationMs;
static String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) return bearerToken.substring(7);
return null;
}
public String generateJwtAccessToken(String username) {
return generateTokenFromUsername(username, jwtExpirationMs);
}
public String generateJwtRefreshToken(String username) {
return generateTokenFromUsername(username, jwtRefreshExpirationMs);
}
private String generateTokenFromUsername(String username, Long expiration) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + expiration))
.signWith(secretKey())
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(secretKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateJwtToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(secretKey()).build().parseClaimsJws(token);
return true;
} catch (SignatureException e) {
JwtUtils.log.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
JwtUtils.log.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
JwtUtils.log.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
JwtUtils.log.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
JwtUtils.log.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
private SecretKey secretKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
}
WebSecurityConfig
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private UserDetailsServiceImpl userDetailsService;
#Bean
private static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
private static JwtAuthorizationFilter authenticationJwtTokenFilter() {
return new JwtAuthorizationFilter();
}
// #Override
// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
// throws Exception {
// authenticationManagerBuilder
// .userDetailsService(userDetailsService)
// .passwordEncoder(WebSecurityConfig.passwordEncoder());
// }
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.cors()
.and()
.csrf()
.disable()
// No session will be created or used by spring security
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/v1/auth/**", "/confirm-account")
.permitAll()
.anyRequest()
.authenticated();
// Apply JWT
http.addFilterBefore(
WebSecurityConfig.authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
Problem solver ?
#Bean
public JwtAuthorizationFilter authenticationJwtTokenFilter() {
return new JwtAuthorizationFilter();
}
I found out that the private static JwtAuthorizationFilter
authenticationJwtTokenFilter() method is calling the issue. If i change that to a public method it is working, although passwordEncoder is privat static and is used in my auth service . Unfortunately it is my plugin which is causing that problem. It changes the code automatically. Does somebody know what to do ?
AuthService class
#Service
public class AuthService implements AuthServiceRepository {
#Autowired private RoleRepository roleRepository;
#Autowired private UserRepository userRepository;
#Autowired private PasswordEncoder encoder;
#Autowired private JwtUtils jwtUtils;
#Autowired private AuthenticationManager authenticationManager;
#Autowired private UserService userService;
#Autowired private EmailService emailService;
#Autowired private ConfirmationTokenRepository confirmationTokenRepository;
#Override
public JwtResponse signUpUser(SignUpRequest signUpRequest, String siteURL)
throws MessagingException, UnsupportedEncodingException {
String accessToken = jwtUtils.generateJwtAccessToken(signUpRequest.getUsername());
String refreshToken = jwtUtils.generateJwtRefreshToken(signUpRequest.getUsername());
Set<Role> roles = SignUpRequest.getRoles(signUpRequest, roleRepository);
AppUser user =
new AppUser(
signUpRequest.getUsername(),
signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()));
user.setRoles(roles);
user.setEnabled(false);
AppUser newUser = userService.saveUser(user);
ConfirmationToken confirmationToken = new ConfirmationToken(user);
confirmationTokenRepository.save(confirmationToken);
emailService.sendVerificationEmail(user, siteURL, confirmationToken.getConfirmationToken());
List<String> userRoles =
newUser.getRoles().stream().map(role -> role.getName().name()).collect(Collectors.toList());
return new JwtResponse(
accessToken,
refreshToken,
user.getId(),
signUpRequest.getUsername(),
signUpRequest.getEmail(),
userRoles);
} ... }
That's because JwtUtils and WebSecurityConfig are not registered as Spring Beans. Try to add a #Component annotation to the classes.
More about dependency injection in Spring Boot: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.spring-beans-and-dependency-injection
I'm trying to setup a simple Oauth2 Server with Spring Boot with only client_credentials flow for now, using in memory users and clients etc. Basically the most basic project so I can build on it. Haven't used Oauth2 with Spring for some time so I'm a bit stuck. I can get access tokens but it seems Spring does not actually validate the username/password sent by the client. Here are the two config classes (AuthorizationServerConfigurerAdapter, WebSecurityConfigurerAdapter):
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userService;
#Value("${jwt.signing-key}")
private String jwtSigningKey;
#Value("${jwt.accessTokenValidititySeconds}")
private int accessTokenValiditySeconds;
#Value("${jwt.refreshTokenValiditySeconds}")
private int refreshTokenValiditySeconds;
public OAuthConfiguration(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userService) {
this.authenticationManager = authenticationManager;
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test-client-id")
.secret(passwordEncoder.encode("test-client-secret"))
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.authorizedGrantTypes("client_credentials")
.scopes("read", "write")
.resourceIds("api");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userService)
.authenticationManager(authenticationManager);
}
#Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(jwtSigningKey);
return converter;
}
}
and:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
public WebSecurityConfiguration(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
#Bean
public DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1")
.password("$2a$10$sWszOXuTlN0amQi8vXp4cerb.tJUQo.4FzLAnTCsSqChsYhlLdQWW")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
}
}
So I can call the "/oauth/token" endpoint with any username/password I get an access token. However the client ID and client secret are being validated.
Any help is appreciated
It's the default behaviour with grant_type client_credentials. So, you don't need to pass the username and password to the token endpoint with this grant_type. In this flow, AuthorizationServer will only validate the client Id and client secret.
POST /token
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
I try to write OAuth provider using spring boot but I got following issue, I using Postman using /oauth/token, but it return 401 Unauthorized and no response..
Here is example link I try to follow: https://codeaches.com/spring-cloud-security/oauth2-authorization-jdbc-token-store
In my requirement I need to use MS SQL Server database, so I follow the script in the link and try to convert to SQL Server script, and also here is the code part I try:
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
#Bean("userDetailsService")
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean("authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> cfg = auth.jdbcAuthentication()
.passwordEncoder(passwordEncoder()).dataSource(dataSource);
cfg.getUserDetailsService().setEnableGroups(true);
cfg.getUserDetailsService().setEnableAuthorities(false);
}
}
CustomTokenEnhancer.java
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
((DefaultOAuth2AccessToken) accessToken).setTokenType("Bearer");
return accessToken;
}
}
OAuth2AuthorizationServer
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.passwordEncoder(passwordEncoder).tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
I know some of codes are already depreciated, but in my requirement it cannot connect outside world (I don't think they want to open the firewall for that..)
Back to the question, does anything I miss?
I`m developing a microservice structure using spring-boot, this structure has an external oauth2 Authorization Server and multiples Resource Servers.
My problem is, each http request to my resources, calls a url to my Authorization Server, to validate the token ( .../oauth/check_token/). This way there is a lot of requests. There is a way to validate/check this token only when it is expired?
My Resource servers:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${security.oauth2.resource.id}")
private String resourceId;
#Value("${security.oauth2.resource.token-info-uri}")
private String tokenInfoUri;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(ADMIN_ANT_MATCHER).hasRole("ADMIN")
.antMatchers(PROTECTED_ANT_MATCHER).hasRole("USER")
.and()
.csrf().disable();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService()).resourceId(resourceId).stateless(true);
}
#Bean
#Primary
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(tokenInfoUri);
tokenService.setClientId(clientId);
tokenService.setClientSecret(clientSecret);
return tokenService;
}
}
Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${security.oauth2.resource.id}")
private String resourceId;
#Value("${security.oauth2.client.access-token-validity-seconds}")
private Integer tokenValidateSeconds;
#Value("${security.oauth2.client.token-secret}")
private String tokenSecret;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private TokenStore tokenStore;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private OauthAccessTokenRepository oauthAccessTokenRepository;
#Autowired
private OauthRefreshTokenRepository oauthRefreshTokenRepository;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(this.tokenStore)
.tokenEnhancer(tokenEnhancer())
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(clientId)
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.authorities("ROLE_USER","ROLE_ADMIN")
.scopes("read","write","trust")
.resourceIds(resourceId)
.accessTokenValiditySeconds(tokenValidateSeconds)
.secret(bCryptPasswordEncoder().encode(clientSecret));
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new TokenEnhancer() {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
CustomUser user = (CustomUser) authentication.getPrincipal();
String token = JwtTokenHelper.generateToken(
user.getIdUser(),
user.getTenantID(),
user.getIdEntity(),
user.getIdBusinessUnit(),
user.getProfile(),
tokenValidateSeconds,
tokenSecret
);
((DefaultOAuth2AccessToken) accessToken).setValue(token);
return accessToken;
}
};
}
}
You can remove the need to use the check_token endpoint, by using signed JWT tokens.
When the resource server receive a JWT token, it verify it's signature by using a public key, and the expiration date by checking the corresponding field in the JSON object.
For this you can use the JwtAccessTokenConverter, JwtTokenStore and the nimbus-jose-jwt library.
The downside of this approach is that you cannot revoke a token. It's then preferable to have short lived tokens.
I have implemented the JWT Application
I am using JWT 0.9.1 version
I am able to generate the Json web token, but when I pass to the http://localhost:8080/api/v1/greeting method I am always getting the same error like 401 Unauthorized.
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#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());
}
/*#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable().authorizeRequests().antMatchers("/").permitAll();
}*/
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/api/v1/authenticate", "/api/v1/register","/api/v1/basicauth")
/*.permitAll().antMatchers(HttpMethod.OPTIONS, "/**")*/
// all other requests need to be authenticated
.permitAll().anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
GreetingController.java
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/api/v1")
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}