I'm trying to build auth system with Spring Boot Security.
So I have custom auth provider (without #Component annotation)
public class CustomAuthProvider extends DaoAuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (authentication.isAuthenticated()) {
return authentication;
}
if ("user".equals(name) && "password".equals(password)) {
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<GrantedAuthority>(Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))));
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
which declared as a bean
#Bean
public DaoAuthenticationProvider authProvider() {
final CustomAuthProvider authProvider = new CustomAuthProvider();
authProvider.setUserDetailsService(userDetailsService);
return authProvider;
}
Here is configuration:
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/anonymous*").anonymous()
.antMatchers("/login*").permitAll()
.antMatchers("/user/registration*").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginProcessingUrl("/login");
}
When I'm trying post query to localhost:8080/login I'm getting sign-in form with message
No AuthenticationProvider found for
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
spring's default request parameters for authentication are: username and password
you are sending email instead of username so it throws an exception somewhere in your authentication manager.
if you want to override the default values you can simply specify that in your HTTP security configs:
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")
Related
I am trying to practice spring security and this is my spring security configuration
#Configuration
#EnableWebSecurity
public class ProjectConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationProvider authenticationProvider;
#Autowired
AuthenticationManagerBuilder builder;
#Bean
public AuthenticationManager global() throws Exception {
builder
.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin")
.password("123")
.authorities(() -> "ADMIN");
return builder.build();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/hello")
.authorizeRequests()
.anyRequest()
.authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.parentAuthenticationManager(global());
}
}
and this is my custom authentication provider:
#Component
public class CustomProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return BadCredentialsException("error");
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
}
I read about how we can create a parent for authentication manager and tried to test it. every time I make a request using Postman I get 403 error. what is wrong with my configuration?
Postman
First Spring uses ProviderManager class as the implementation of AuthenticationManager interface and if you see the implementation of the authenticate method you figure out that it only uses parent authentication manager if the child result is null not exception in your case.
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
// other stuff
}
so change the following code in CustomProvider to returns null
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return AuthenticationException("error");
}
also from the image I noticed you are using http basic authentication but you didn't enable it in your configuration.
http
.antMatcher("/hello")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
I try to generate a JWT Token with Spring Security but I don't how to do it correctly (with the best practice).
Do I should "intercept" the authenticate method within UsernamePasswordAuthenticationFilter somewhere and generate token inside it ?
Or it is better to use AuthenticationManager autowired in the controller '/login' ?
I'm afraid to authenticate the user twice if I use the controller mechanism.
I used this tutorial : tutorial Jwt Token
Here is my code :
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
// #Autowired
// private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**", "/login/**", "/register/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
.formLogin()
.usernameParameter("email")
//.loginPage("http://localhost:4200/login").failureUrl("/login-error")
.and()
.logout()
.permitAll();
http
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public CustomDaoAuthenticationProvider authenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebConfig() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "Authorization", "Cache-Control",
"Access-Control-Allow-Origin")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.allowCredentials(true).maxAge(3600);
}
};
}
}
Token Filter
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(req, res);
}
}
Token Provider
#Component
public class JwtTokenProvider {
#Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";
#Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h
#Autowired
private UserDetailsService userDetailsService;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
Given your context, the controller is responsible for issuing a new token (after validating credentials) while the filter is responsible for authenticating the user against the given token. The controller should not populate the security context (authenticate user), it is the filter's responsibility.
To better understand the two phases:
Spring uses two filters to authenticate and log in a user.
See UsernamePasswordAuthenticationFilter and SecurityContextPersistenceFilter in a "username/password" scenario, from the Spring Security project: the first one processes an authentication attempt (username/password) while the latter populates the security context from a SecurityContextRepository (from a session in general).
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.
I have a Spring Boot application. It has a welcome page which users select their login type and they're redirected to a login page and get their role based on their selection. Each login page provides authentication mechanisms with different external web services. I configured my security for a scenario, but how to do this for multiple scenarios? Should I do it with multiple security configs, or all configurations in the same security config? If so how?
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider cap;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/welcomeX").hasAuthority("X_USER")
.and()
.formLogin()
.loginPage("/login")
.loginPage("/main")
.loginProcessingUrl("/welcome")
.permitAll()
.failureUrl("/login?error=true");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(cap);
}
CustomAuthenticationProvider.java
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ExternalService externalService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Response resp = externalService.authenticate(username, password);
if (resp.isSuccess()) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("X_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
return auth;
} else {
return null;
}
}
}
You can define multiple WebSecurityConfigurerAdapters in a #Configuration. http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#multiple-httpsecurity
This question is actually related to this issue problem.
Based on the suggestion from #harsh-poddar, I added the filter accordingly.
However, after adding that it seems like I can't login even with valid credential.
Following is the related code:
SecurityConfig
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #Bean
// public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
// return new CustomAuthenticationEntryPoint();
// }
#Bean
public CustomExceptionTranslationFilter customExceptionTranslationFilter() {
return new CustomExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//Note : Able to login without this filter, but after adding this, valid credential also fails
.addFilterAfter(customExceptionTranslationFilter(), ExceptionTranslationFilter.class)
// .exceptionHandling()
// .authenticationEntryPoint(new customAuthenticationEntryPoint())
// .and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider());
}
}
CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider() {
super();
}
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final String name = authentication.getName();
final String password = authentication.getCredentials().toString();
if (name.equals("admin") && password.equals("password")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
final UserDetails principal = new User(name, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
throw new BadCredentialsException("NOT_AUTHORIZED");
}
}
#Override
public boolean supports(final Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
CustomExceptionTranslationFilter
#Component
public class CustomExceptionTranslationFilter extends ExceptionTranslationFilter {
public CustomExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationEntryPoint);
}
}
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized.");
}
}
p/s : sorry for the basic question, I'm really new in spring & spring security.
The intended design for AuthenticationEntryPoint is to start/initiate an authentication. However, your implementation CustomAuthenticationEntryPoint does not do this. Instead, it simply sends back an unauthorized response. Please see javadoc for AuthenticationEntryPoint for more details on implementation specifics.
Based on your configuration you are using HTTP Basic for authentication:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
This specific configuration will automatically configure BasicAuthenticationEntryPoint which is an implementation of AuthenticationEntryPoint. The BasicAuthenticationEntryPoint will challenge the user with a http response header of WWW-Authenticate: Basic realm="User Realm" to authenticate, as per server protocol.
However, the fact that you are configuring your own CustomAuthenticationEntryPoint it will ultimately override the BasicAuthenticationEntryPoint which is not what you want to do.
The other post recommended this configuration which again is not what you want to do.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
If your main goal is to provide a custom response to the user when authentication fails than I would propose a form login configuration with a configured AuthenticationFailureHandler. Here is the configuration:
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().failureHandler(new DefaultAuthenticationFailureHandler())
.and()
.csrf().disable(); // NOTE: I would recommend enabling CSRF
Your implementation of DefaultAuthenticationFailureHandler would be:
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// Set status only OR do whatever you want to the response
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
The AuthenticationFailureHandler is specifically designed to handle a failed authentication attempt.