I have my spring app with a login with spring security, it works fine, but I want to do something additional.
There are some users that will be logged throught another method, so, I will get a post with the data at my controllers... is there any way from that controller simulate that the user is actually entering his user/password at the login form and then create a session on spring security?
Right now I have this
Spring Securit Configuration
#Autowired
private UserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/**").authenticated();
http.
authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/admin/**")
.access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/**")
.access("hasRole('ROLE_USER')")
.and()
.formLogin()
.defaultSuccessUrl("/usuario/home")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
if(encoder == null) {
encoder = new BCryptPasswordEncoder();
}
return encoder;
}
Login method at my controller (nothing much really..)
#RequestMapping("/login")
public ModelAndView login() {
ModelAndView mvc = new ModelAndView();
mvc.setViewName("login");
mvc.addObject("message", "");
return mvc;
}
I have my details service as well , like this
#Autowired
private UserRepository userRepository;
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username)
throws UsernameNotFoundException {
com.jp.base.domain.User user = userRepository.findByUsername(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRoles());
System.out.println("user roles: " + user.getUserRoles());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.jp.base.domain.User user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
return new ArrayList<GrantedAuthority>(setAuths);
}
}
Any idea how to do this??
Thanks.
There is a way to do that. You can utilize Spring SecurityContextHolder. It would look something like this:
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, credentials);
SecurityContextHolder.getContext().setAuthentication(authentication);
where principal is UserDetails object. If you don't have credentials, you can just pass null.
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_USER");
grantedAuthorities.add(ga);
Authentication auth = new UsernamePasswordAuthenticationToken(user.getUid(), "", grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(auth);
Related
I am using Spring boot 2.5.6 with webflux security.
#EnableWebFluxSecurity
public class AdminSecurityConfig {
#Bean
public SecurityWebFilterChain securitygWebFilterChain(final ServerHttpSecurity http,
final ReactiveAuthenticationManager authManager,
final ServerSecurityContextRepository securityContextRepository,
final MyAuthenticationFailureHandler failureHandler) {
http.securityContextRepository(securityContextRepository);
return http.authorizeExchange().matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.pathMatchers(props.getSecurity().getIgnorePatterns()).permitAll()
.pathMatchers("/api/v1/service/test").hasAuthority("DEFAULT")
.anyExchange().authenticated()
.and()
.formLogin()
.loginPage("/login")
.authenticationSuccessHandler(authSuccessHandler())
.and()
.exceptionHandling()
.authenticationEntryPoint((exchange, exception) -> Mono.error(exception))
.accessDeniedHandler((exchange, exception) -> Mono.error(exception))
.and()
.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Bean
public ReactiveAuthenticationManager authenticationManager() {
final UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(
userDetailsService);
authenticationManager.setPasswordEncoder(passwordEncoder());
return authenticationManager;
}
#Bean
public ServerSecurityContextRepository securityContextRepository() {
final WebSessionServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();
securityContextRepository.setSpringSecurityContextAttrName("my-security-context");
return securityContextRepository;
}
}
Mono<Principal> principal = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).cast(Principal.class);
final MyAppUserDetails user = (MyAppUserDetails) ((UsernamePasswordAuthenticationToken) principal)
.getPrincipal();
Here I am able to retrieve the logged in user details. The MyAppUserDetails will have user details like firstName, lastName, email, user id, org id, ....
Now, I would like to update the user details in session after the user is logged in, say change the user name without asking the user to logout and login.
I tried the code below, but not sure how to set the credentials and set the updated user into the security context so that the next get current user call from security context will return the updated user.
final MyAppUserDetails appUser = new MyAppUserDetails("firstName", "lastName", "email", 1, 4);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(appUser, ....);
ReactiveSecurityContextHolder.withAuthentication(authentication);
Get session from request.
Get context from session
Create new user
Set the new user in the context
public Mono<ServerResponse> updateSession(ServerRequest request) {
return request.exchange().getSession().flatMap(session -> {
final SecurityContext context = session.getAttribute("app-security-context");
AppUserDetails newUser = AppUserDetails.of(...);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(newUser, pwd, authorities);
context.setAuthentication(token);
}
}
It is working, but not sure why do we need to save the context with the approach below.
serverSecurityContextRepository.save(request.exchange(), context);
It works without the above call.
im in a very early developing of this app made in SpringBoot and Mysql with front of Angular , and trying to implement on my security process the register or creation of user , when i check on my swagger or postman i receive this json error:
{
"timestamp": "2020-11-03T17:53:58.048+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/login/renter"
}
On my humble logic that makes me think i got some miss-typed URI, but thats not the case.
Here some of the process
SECURITY CONFIGURATION
package com.example.demo.security;
....some imports....
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
RenterService renterService;
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/v2/api-docs",
"/register/renter",THIS ONE IS WHAT IM TRYING TO REACH AT
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login/renter")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}//permissions per endpoint
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(renterService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
}
For some reason just got me in the login page('login/renter'), not recognizing the url permitted formerly
Then my controller(In this case im trying to register a user thus the controller im exposing is the one referring to that process
REGISTER CONTROLLER
package com.example.demo.controller;
.....some imports....
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
public class RegistrationController {
#Autowired
RenterService renterService;
#ModelAttribute("renterCreated")
public RenterRegisterDto renterRegisterDto() {
return new RenterRegisterDto();
}
#GetMapping
public String showRegistrationFormRenter(Model model) {
return "registration";
}
public static final Logger logger = LoggerFactory.getLogger(RegistrationController.class);
#RequestMapping(value = "register/renter", method = RequestMethod.POST)
public String createUser(#ModelAttribute("renterCreated") #Valid RenterRegisterDto renterCreated, BindingResult result) throws GeneralException {
Role newRole = new Role();
newRole.setRoleType("User");
if (renterService.findByEmail(renterCreated.getRenterEmail()) != null) {
logger.error("user with email" + renterCreated.getRenterEmail() + "is already registered");
result.rejectValue("email", null, "There is already an account with this email");
}
if (result.hasErrors()) {
return "registration";
}
renterService.save(renterCreated);
return "userCreated";
}
}
On my service literally trigger the process of creating a user , asigning it a role and encryptying his password
RENTER SERVICE
public interface RenterService extends UserDetailsService {
Renter findByEmail(String renterEmail) throws GeneralException;
Renter findRenterById(Long id)throws GeneralException;
Renter save(RenterRegisterDto registerDto);
}
RENTER SERVICE IMPLEMENTATION
#Service
public class RenterServiceImpl implements RenterService {
#Autowired
private RenterRepository renterRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
public Renter findByEmail(String renterEmail) throws GeneralException {
return renterRepository.findByRenterEmail(renterEmail).orElseThrow(() -> new NotFoundException("SNOT_404_1", "EmailNotFound"));
}
public Renter findRenterById(Long id) throws GeneralException {
return renterRepository.findById(id).orElseThrow(() -> new NotFoundException("SNOT", "Id not found"));
}
public Renter save(RenterRegisterDto registerDto) {
Renter renter = new Renter();
Role role = new Role();
renter.setRenterName(registerDto.getRenterName());
renter.setRenterEmail(registerDto.getRenterEmail());
renter.setRenterPassword(passwordEncoder.encode((registerDto.getRenterPassword())));
renter.setRole(new Role(role.getRoleType()));
return renterRepository.save(renter);
}
public UserDetails loadUserByUsername(String renterEmail) throws UsernameNotFoundException {
Renter renter = renterRepository.findByRenterEmail(renterEmail).orElseThrow(() -> new UsernameNotFoundException("Invalid username or password."));
return new org.springframework.security.core.userdetails.User(renter.getRenbterEmail(),renter.getRenterPassword(),mapRoleUser(Arrays.asList(renter.getRole().getRoleType())));
}
private static List<GrantedAuthority> mapRoleUser(List<String> roles){
List<GrantedAuthority>authorities=new ArrayList<>();
for (String role : roles){
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
And the RegisterUserDto class just would set the string variables referring every field of the sign up form neccesary in the process to be sent to the Back(i won't expose all the code cause is already too much )
As far as i could humbly understand guess the problem would be located in my controller registration process , specifically the endpoint , and its permission in the spring security congifuration(which by the way i double checked are the same url) reason why when i try to trigger the process aiming to that endpoint pasing all the data automatically refuses it and send me to the login page with endpoint(login/renter)
Any idea about why is this happening?
Thanks in advance!!!
I am setting a server with spring boot 2 and angular 6, so far I've done several things with some tutorials and some things by myself but that's not the question.The question and my actual problem is that I am trying to create login so but for some reason I am not able to get it working after I remove .httpBasic and replace it with .formLogin.loginPage I got principal call with null.After I enable httpBasic again I submit my user/pass it works fine but I cannot get it to work without it.As well my angular client is buggy as hell and I don't actually know from where the problem is.
LoginRest.java
#RestController
#CrossOrigin(origins = "http://localhost:4200")
public class LoginRest {
#GetMapping(value = "/login",produces = "application/json")
public Principal login(Principal principal) {
System.out.println("/login called " + principal);
return principal;
}
UserPrincipal.java
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
this.user.getRoles().forEach(r -> {
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + r.getRoleName());
authorities.add(authority);
});
return authorities;
}
SecurityConfiguration.java
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/profile").authenticated()
.antMatchers("/admin/users").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login")
.and()
.csrf()
.disable();
}
#Bean
DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userPrincipalDetailsService);
return daoAuthenticationProvider;
}
UserPrincipalDetailsService.java
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = this.userService.getByEmail(email);
UserPrincipal userPrincipal = new UserPrincipal(user);
return userPrincipal;
}
and this is my angular 6 project https://bitbucket.org/rumenyavorov/springboot-client/
I just want to get my login working and proceed further with my project and to understand what the hell I was doing wrong.
Thanks in advance
I am securing my spring application with spring security. I have implemented UserDetailService for authenticating user. When logging in from Angular 4, UserDetailService is not invoked and user is not found.
The reason I found is I have not used formLogin() in SecurityConfig.java. For that I have added httpBasic() but its not working. Is there any configurations missing ?
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LoginUserDetailService loginUserDetailService;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(loginUserDetailService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.authorizeRequests()
.antMatchers("/login","/logout", "/home").permitAll()
.anyRequest().fullyAuthenticated().and()
.logout()
.permitAll()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
.and()
.httpBasic().and()
.csrf().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
}
}
Controller
#RequestMapping("/login")
public Principal user(Principal principal) {
logger.info("user logged " + principal);
return principal;
}
LoginUserDetailService
#Service("loginUserDetailService")
public class LoginUserDetailService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
System.out.println("Login User Detail Service");
System.out.println("email: "+email);
UserEntity user = userRepository.findByEmail(email);
if (user != null) {
System.out.println("User found ");
}
else {
System.out.println("User not found");
}
Set<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
System.out.println("Authorities of user : " + email);
authorities.forEach(a -> System.out.println(a));
return buildUserForAuthentication(user, authorities);
}
private Set<GrantedAuthority> getUserAuthority(Set<RoleEntity> userRoles) {
System.out.println("Roles: ");
for(RoleEntity role : userRoles) {
System.out.println(role);
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (RoleEntity role : userRoles) {
authorities.add(new SimpleGrantedAuthority(role.getRole()));
role.getPermissions().stream().map(p -> new SimpleGrantedAuthority(p.getPermission()))
.forEach(authorities::add);
}
System.out.println("Authorities :");
for(GrantedAuthority authority : authorities)
{
System.out.println(authority);
}
return authorities;
}
private UserDetails buildUserForAuthentication(UserEntity user, Set<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
user.getActive(), true, true, true, authorities);
}
}
Log
2018-10-24 12:45:26.532 INFO 9660 --- [nio-8080-exec-2] c.m.c.AuthenticationController : user logged null
Its solved by passing user credentials in request header of http GET method instead of passing credentials as a body of http POST method from angular client.
In my spring application, the login process is handled by spring security using the UserDetailsService and BCryptPassword classes. When the login is successful, this class redirect the use to the main page:
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
HttpSession session = request.getSession();
SavedRequest savedReq = (SavedRequest) session.getAttribute(WebAttributes.ACCESS_DENIED_403);
if (savedReq == null) {
response.sendRedirect(request.getContextPath() + "/acesso/home");
}
else {
response.sendRedirect(request.getContextPath() + "/acesso/login?erro=no_permit");
}
}
}
In the jsp page, I can get the username using this expression:
${pageContext.request.remoteUser}
But, in my database, I also have stored the first and last name. I need, in the class above (or in any place possible), pass this data to my view. I try this:
request.getSession().setAttribute("username", "Usuario Teste");
using this in the view: ${username}, but when I run the application, nothing is displayed.
Anyone can point me a way to do that?
my spring security configuration
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/resources/**", "/publico/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/acesso/login").permitAll()
.loginProcessingUrl("/login").permitAll()
.usernameParameter("login")
.passwordParameter("senha")
.successHandler(new CustomAuthenticationSuccessHandler())
.failureHandler(new CustomAuthenticationFailureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/acesso/login").permitAll();
}
UPDATE
Authentication Provider
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService usuario;
#Autowired
private BCryptPasswordEncoder encoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails user = usuario.loadUserByUsername(name);
if(encoder.matches(user.getPassword(), password)) {
Authentication auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
return auth;
}
else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
UserDetailsService
#Service
public class AcessoService implements UserDetailsService {
#Autowired
private UsuarioHome accountDao;
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario account = accountDao.findByField("login", username);
if(account==null) {
System.out.println("No such user: " + username);
throw new UsernameNotFoundException("No such user: " + username);
} else if (account.getAutorizacao().isEmpty()) {
System.out.println("User " + username + " has no authorities");
throw new UsernameNotFoundException("User " + username + " has no authorities");
}
List<Permissao> lista = new ArrayList<Permissao>();
int max = account.getAutorizacao().size();
for(int i=0; i<max; i++) {
int max2 = account.getAutorizacao().get(i).getPermissao().size();
for(int j=0; j<max2; j++) {
lista.add(account.getAutorizacao().get(i).getPermissao().get(j));
}
}
boolean accountIsEnabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(account.getLogin(), account.getSenha(), accountIsEnabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(lista));
}
public List<String> getRolesAsList(List<Permissao> list) {
List <String> rolesAsList = new ArrayList<String>();
for(Permissao role : list){
rolesAsList.add(role.getNome());
}
return rolesAsList;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
public Collection<? extends GrantedAuthority> getAuthorities(List<Permissao> list) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRolesAsList(list));
return authList;
}
}
Yor are storing your "username" data in session.
You can try to get it in one of this two ways.
request.setAttribute("username", "Usuario Teste"); and then yo can use ${username} EL directly. Or you can use request.getSession().setAttribute("username", "Usuario Teste"); with ${sessionScope.username} EL.
You must to know the differents between session and request scopes.
PD:
Read this.
In another side, to get the username in Spring Sec yo can try to use <sec:authentication property="principal.username" />
And, if you want to use more complex objects than one String as principal, you can extends the UsernamePasswordAuthenticationToken.