I am very new to java spring security, and was following the Spring.io tutorial guide.
As part of this, I edited the WebSecurityConfig class as required:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
Within the userDetailService() method, it uses withDefaultPasswordEncoder() which is now deprecated as seen in the docs: withDefaultPasswordEncoder()
Unfortunately, I have not been able to find an alternative to this, to complete this tutorial without using the deprecated method.
Would somebody be able to provide an alternative for this if possible?
Thanks!
note: I have attached a couple of screen shots of my error, as well as my gradle file
EDIT: deleted old answer, misunderstood the question. Here's the new one:
User.withDefaultPasswordEncoder() can still be used for demos, you don't have to worry if that's what you're doing - even if it's deprecated - but in production, you shouldn't have a plain text password in your source code.
What you should be doing instead of using your current userDetailsService() method is the following:
private static final String ENCODED_PASSWORD = "$2a$10$AIUufK8g6EFhBcumRRV2L.AQNz3Bjp7oDQVFiO5JJMBFZQ6x2/R/2";
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("user").password(ENCODED_PASSWORD).roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Where ENCODED_PASSWORD is secret123 encoded with BCrypt. You can also encode it programmatically like so: passwordEncoder().encode("secret123").
That way, even if you push your code to a public repository, people won't know the password because ENCODED_PASSWORD only shows the encoded (and hashed) version of the password and not the plain text version, but because you know that $2a$10$AIUufK8g6EFhBcumRRV2L.AQNz3Bjp7oDQVFiO5JJMBFZQ6x2/R/2 is actually the encoded password of the string secret123 whereas others don't, your in-memory user with the credentials user:secret123 won't be compromised.
Note that I'm using leaving it in a static variable for the sake of the example.
In spring security 5.7.3 WebSecurityConfigurerAdapter will be deprecated and so we have
to provide bean of UserDetailsService interface.
Sample code
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(form -> {
form.loginPage("/login")
.permitAll();
}).authorizeRequests();
return http.build();
}
#Bean
#Description("In memory Userdetails service registered since DB doesn't have user table ")
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
UserDetails user = User.builder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Using the passwordEncoder.encode() would be like this
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("user")
.password(passwordEncoder().encode("miClave"))
.roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The withDefaultPasswordEncoder() method is now deprecated in favor of passwordEncoder() which accepts a PasswordEncoder implementation. It's recommended to use a stronger password encoding mechanism such as bcrypt or scrypt.
To use bcrypt, you can do the following:
package com.example.securingweb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
#Bean
public UserDetailsService userDetailsService() {
UserDetails user =
User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The below code is the new implementation of WebSecurityConfig.java file https://spring.io/guides/gs/securing-web/
Related
Spring is updated, says authorizeRequests is deprecated, antMatchers removed. Can someone show how SpringSecurity should looks like rn?
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(prePostEnabled = false, securedEnabled = true)
public class SecurityConfig {
private final PersonDetailsService personDetailsService;
#Autowired
public SecurityConfig(PersonDetailsService personDetailsService) {
this.personDetailsService = personDetailsService;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeHttpRequests(authorize -> authorize
.requestMatchers("/, /login, /signup, /logout").permitAll()
.requestMatchers("/api").hasRole("ADMIN")
.requestMatchers("/user").hasRole("USER")
.anyRequest().authenticated())
.logout().logoutUrl("/logout").logoutSuccessUrl("/").and()
.formLogin().loginPage("/login").loginProcessingUrl("/login").defaultSuccessUrl("/user").failureUrl("/login?error");
return http.build();
}
}
Read the documentation, stackoverflow, etc. didn't find the solution.
You can use authorizeHttpRequests instead of authorizeRequests and requestMatchers instead of antMatchers.
For example:
http.authorizeHttpRequests()
.requestMatchers("/authentication/**").permitAll()
.requestMatchers("/h2/**").permitAll()
.anyRequest().authenticated();
I created a password encoder bean and I am just calling passwordEncoder.encode() method in UserService -> createUser() method. But how spring is understanding that "I have to use password encoder when login request came". I am not passing passwordEncoder as an argument anywhere.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
private final JwtFilter jwtFilter;
private final JwtAuthenticationEntryPoint authenticationEntryPoint;
private final JWTAccessDeniedHandler accessDeniedHandler;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.headers().frameOptions().disable().and()
.csrf().disable()
.cors().and()
.authorizeRequests(auth -> {
auth.antMatchers("/api/admin").hasAuthority("ADMIN");
auth.antMatchers("/api/user").hasAnyAuthority("ADMIN", "USER");
auth.anyRequest().authenticated();
})
.formLogin().disable()
.httpBasic().disable()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/api/public", "/h2-console/**", "/api/auth/login");
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
};
}
}
The default password encoder in Spring delegates to any defined beans of type org.springframework.security.crypto.password.PasswordEncoder. So Spring is simply delegating to the bean that you provided.
If you see the code here
https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java#L332
You would see that Spring actually injects the bean PasswordEncoder from the current applicationContext
So as you see creating this bean is what the Spring security calls when encoding the password coming into the AuthenticationManager
As I see Spring Security OAuth2.x project was moved to Spring Security 5.2.x. I try to implement authorization and resource server in new way. Everythin is working correctly except one thing - #PreAuthorize annotation. When I try to use this with standard #PreAuthorize("hasRole('ROLE_USER')") I always get forbidden. What I see is that the Principal object which is type of org.springframework.security.oauth2.jwt.Jwt is not able to resolve authorities and I have no idea why.
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#44915f5f: Principal: org.springframework.security.oauth2.jwt.Jwt#2cfdbd3; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#ffffa64e: RemoteIpAddress: 172.19.0.1; SessionId: null; Granted Authorities: SCOPE_read, SCOPE_write
And claims after casting it to Jwt
{user_name=user, scope=["read","write"], exp=2019-12-18T13:19:29Z, iat=2019-12-18T13:19:28Z, authorities=["ROLE_USER","READ_ONLY"], client_id=sampleClientId}
Security Server Configuration
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public KeyPair keyPair() {
ClassPathResource ksFile = new ClassPathResource("mytest.jks");
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(ksFile, "mypass".toCharArray());
return keyStoreKeyFactory.getKeyPair("mytest");
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyPair());
return converter;
}
#Bean
public JWKSet jwkSet() {
RSAKey key = new Builder((RSAPublicKey) keyPair().getPublic()).build();
return new JWKSet(key);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
public SecurityConfiguration(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/.well-known/jwks.json")
.permitAll()
.anyRequest()
.authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
Resource server configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResuorceServerConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
Maybe someone had similar issue?
By default, the resource server populates the authorities based on the "scope" claim.
If the Jwt contains a claim with the name "scope" or "scp", then Spring Security will use the value in that claim to construct the authorities by prefixing each value with "SCOPE_".
In your example, one of the claims is scope=["read","write"].
This means that the authority list will consist of "SCOPE_read" and "SCOPE_write".
You can modify the default authority mapping behaviour by providing a custom authentication converter in your security configuration.
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthenticationConverter());
Then in your implementation of getJwtAuthenticationConverter, you can configure how the Jwt maps to the list of authorities.
Converter<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
// custom logic
});
return converter;
}
Add this to your SecurityConfig
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
And the converter
private JwtAuthenticationConverter jwtAuthenticationConverter() {
// create a custom JWT converter to map the "roles" from the token as granted authorities
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
I created my Custom UserDetailService and Security Config. When I allow to enter to secure page only authorized users - OK, but if users with roles -
HTTP Status 403 – Forbidden.
I think I do not work with roles correctly. Please, help
My UserService
public interface UserService extends UserDetailsService {
}
#Service
public class UserServiceImpl implements UserService{
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode("1"));
//There is the problem I think
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
roleList.add(new SimpleGrantedAuthority("ADMIN"));
user.setRoleList(roleList);
//
user.setAccountNonExpired(true);
user.setAccountNonLocked(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
return user;
}
}
Security Config
#Configuration
#EnableWebSecurity
#ComponentScan("something")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin*").authenticated() - it works
//.antMatchers("/admin*").hasRole("ADMIN") - it doesn't work
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
My User.class just in case
public class User implements Serializable, UserDetails {
//fields
}
In order to set your user's role to "ADMIN", you need to set the authority to "ROLE_ADMIN".
roleList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
A role is the same as an authority prefixed with "ROLE_".
The order should be like this:
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/admins/**").hasRole("ADMIN")
.antMatchers("/users/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.....
.....
im exploring a little of spring.
i got across spring boot for easy endpoints see:
#Controller
#EnableAutoConfiguration
public class SampleController {
#RequestMapping("/sample")
#ResponseBody
String sample() {
return "Hello sample!";
}
#RequestMapping("/sample2")
#ResponseBody
String sample2() {
return "Hello sample secured!";
}
}
logically the endpoints are accessible on localhost:8181/sample
but on using spring security the "protected" endpoint becames unaccessible because the login page gives me 404
my security class is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/sample" ).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
i am able to access /sample as is not protected. but unable to access /sample2 as it redirects to /login
im configuring my security class according to this guide: https://spring.io/guides/gs/securing-web/
I am able to access /sample as is not protected. But unable to access
/sample2 as it redirects to /login
Because you have not by-passed /sample2 in your security configuration.
.antMatchers("/sample2" ).permitAll()
Another thing is that as you have specified custom login page
.formLogin()
.loginPage("/login")
you have to provide a login page.
Inject userDetailsService into authenticationProvider:
#Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider=new CustomAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(authenticationProvider());
}
Add this configuration to spring security:
.antMatchers("/sample2").hasRole("USER")