All users can login successfully but all of them can open only url which are using permitAll() method. FOr url meta i have set the role "RADMIN" and after the user with that role is logged in he can not open meta or any other url because of 403 ERROR. The url which can be open are only "login", "logout", "home".
#Configuration
#ComponentScan("bg.package")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationService authenticationService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login", "/home", "/logout").permitAll()
.antMatchers("meta/**").hasAuthority("RADMIN")
.anyRequest().authenticated()
.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
Md5PasswordEncoder encoder = new Md5PasswordEncoder();
auth.userDetailsService(authenticationService).passwordEncoder(encoder);
}
}
AuthService
#Service
public class AuthenticationService implements UserDetailsService {
#Autowired
private AuthDao authDao;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
AuthModel authModel = authDao.getUserInfo(email);
GrantedAuthority authority = new SimpleGrantedAuthority(authModel.getRank());
UserDetails userDetails = (UserDetails) new User(authModel.getName(), authModel.getPass(), Arrays.asList(authority));
return userDetails;
}
}
If you are checking the role of a user with hasAuthority() method you should also include prefix ROLE_ before your role name. So the part of your security configuration which checks for role should look like this:
.antMatchers("meta/**").hasAuthority("ROLE_RADMIN")
Alternatively instead of hasAuthority("ROLE_RADMIN") you can use hasRole("RADMIN").
Related
I have built a Java application with the REST API convention. I working on endpoint which returns objects only if object is connected with user by common id in database(ManyToOne annotation). In order to achieve that i need current logged user id for comapring it with object's user id. If Ids are the same, endpoint returns data. I know solutions as "Principal" or "Authentication" classes but they provide everything except of "id". I used spring security http basic for authentication.
My authentication classes:
#Component
public class CustomAuthenticator implements AuthenticationProvider {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public CustomAuthenticator(UserRepository userRepository, #Lazy PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userRepository.findByLogin(login).orElseThrow(() -> new EntityNotFoundException("User not found"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(login, password, convertAuthorities(user.getRoles()));
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities = new HashSet<>();
for (UserRole ur : userRoles) {
authorities.add(new SimpleGrantedAuthority(ur.getRole().toString()));
}
return authorities;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
SECURITY CONFIG CLASS:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticator customAuthenticator;
public SecurityConfig(CustomAuthenticator customAuthenticator) {
this.customAuthenticator = customAuthenticator;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api").permitAll()
.antMatchers("/api/register").permitAll()
//TODO everybody now has access to database, change it later
.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
http
.csrf().disable()
.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticator);
}
}
Does someone know how to resolve that problem ?
You can use UserDetails class and set id for the username field, this class provides by spring security.
If you don't want that solution, you can create a Subclass extend UserDetails class and decide an id field. When receiving the request, parse principal to UserDetails or subclass extends UserDetails to get the id
Ex:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userPrincipal = (UserDetails)authentication.getPrincipal();
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()
.....
.....
The question is: I have an Auth server(spring security) and a Resource server,The Resource server can't get principal user correctly.
Spring Boot Version: 2.0.7
Spring Cloud Version: Finchley.SR2
Firstly, I go to get access token from Auth-server, and It is the success.
Then, I use the token to post the Resource-server, and the controller of Resource-server can get a principal.
However, the principal is a string, not a UserDetails Object.
Auth Server websecurity config like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MaliUserDetailsService maliUserDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(maliUserDetailsService).passwordEncoder(passwordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Auth Server UserDetailsService:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
return user;
}
Resouce Server Controller:
#GetMapping("/test")
public String test(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return "hello " + userDetails.getUsername();
}
When I post to resource server, it cast a exception:
java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.security.core.userdetails.UserDetails (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.security.core.userdetails.UserDetails is in unnamed module of loader 'app')
===============
Then I tried to change to controller code like this:
#GetMapping("/test")
public String test(Authentication authentication) {
return "hello " + authentication.getPrincipal().toString();
}
The result is "hello admin". (admin is my username)
Why the principal in the controller is a string instead of UserDetails Object? How to solve it.
I'm attempting to create a site. The pages that are available to anyone are the landing page, the login page, the register page, the about us page, and the contact page as seen below.
#Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public CustomWebSecurityConfigurerAdapter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/join", "/login", "/about", "/contact").permitAll()
.and().authorizeRequests()
.anyRequest().authenticated()
.and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.logout().logoutSuccessUrl("/").permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Here is my OAuth2 config:
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-client")
.authorizedGrantTypes("client_credentials", "authorization_code")
.authorities("ROLE_CLIENT")
.scopes("read", "write")
.resourceIds("business-resource")
.secret("secret");
}
}
And my controller:
#EnableResourceServer
#RestController
public class BusinessAccountController {
private static final Logger logger = Logger.getLogger(BusinessAccountController.class);
#Autowired
BusinessUserService businessUserService;
/*URL mapping constants for each particular controller method*/
private static final String LOGIN_URL_MAPPING = "/login";
private static final String USER_ENDPOINT_URL_MAPPING = "/user";
private static final String ACCOUNT_CREATION_URL_MAPPING = "/join";
/**
* #param principal
* #return
*/
#RequestMapping(USER_ENDPOINT_URL_MAPPING)
public Principal user(Principal principal) {
return principal;
}
#RequestMapping(value = LOGIN_URL_MAPPING, method = RequestMethod.POST)
public ResponseEntity<BusinessUser> login(#RequestBody BusinessUser inputUser) {
BusinessUser requestedUser = businessUserService.findByUsername(inputUser.getUsername());
if(requestedUser != null)
if(BCrypt.checkpw(inputUser.getPassword(), requestedUser.getPassword()))
return new ResponseEntity<>(requestedUser, HttpStatus.OK);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
...
For some reason, every single page requires an authentication token(gives a 401 unauthorized response), even the ones I specified permitAll() on (login, contact, about us, etc...). Why is Spring asking for an authentication token for the pages I specified to be accessed without authentication?
Side note: If I generate an access token, I'm able to register, log in, and access unauthorized pages. I want to be able to access the specified pages in my WebSecurityConfigurerAdapter without an authentication token.
I have the following Sprring web app:
#Secured({"ROLE_ADMIN"})
#RequestMapping(value = "data/{id}", method = RequestMethod.GET)
public Object getData(#RequestPath String id)
#RequestMapping(value = "login", method = RequestMethod.GET)
public Object login(#RequestParam String username, #RequestParam String password)
In login I need to call another server, pass credentials and get back roles, then let spring know to use these roles for incoming user.
After login client can use getData method if pass authorization of ROLE_ADMIN.
How can I implement this behaviour using java config?
UPDATE:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public AuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LogFactory.getLogger();
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
log.debug("name=" + name + " password=" + password);
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
logger.debug("supports authentication=" + authentication);
return true;
}
}
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
But as I can see from the logs CustomAuthenticationProvider.authenticate is never called.
Did I miss something?
Thanks.
UPDATE 2: correct solution for me:
Remove Login url from authentication config
add exception handler to disable redirection in case of authentication error
add success handler to send user valid json response
use http POST for app/login
#EnableGlobalMethodSecurity(securedEnabled = true) in web config in order to allow #Secured annotation in controller.
Thanks for all prompts.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
**.anyRequest().authenticated()**
.and().formLogin()
.loginProcessingUrl("/login").usernameParameter("username")
.passwordParameter("password")
**.successHandler(authenticationSuccessHandler)**.failureHandler(authenticationFailureHandler)
.and().csrf().disable().**exceptionHandling()
.authenticationEntryPoint(errorsAuthenticationEntryPoint)**;
}
You need to use WebSecurityConfigurerAdapter like this:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutUrl("/myurl/logout")
.and()
.formLogin()
.loginPage("/myurl/login")
.defaultSuccessUrl("/myurl/login?success");
}
}
Every thing is explain in the documentation https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-form
You will need to implement a custom AuthenticationProvider. Something like:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
#Bean
AuthenticationProvider customAuthenticationProvider() {
CustomAuthenticationProvider impl = new CustomAuthenticationProvider();
impl.setUserDetailsService(customUserDetailsService());
/* other properties etc */
return impl ;
}
#Bean
UserDetailsService customUserDetailsService() {
/* custom UserDetailsService code here */
}
}