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.
Related
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'm building a service which is responsible for allowing a user to link external services to their user account.
Authentication of the web app is using a JWT passed in via query string.
I have a Controller that is attempting to use the OAuth2AuthorizedClientManager to initiate the OAuth client workflow to redirect the user to the target external service and authorize the access.
I am struggling to get the Authorization Grant flow to start - with the code below, my OAuth2AuthorizedClient is always null.
I believe I have a disconnect on how to best use the ClientManager.
Controller:
private final OAuth2AuthorizedClientManager authorizedClientManager;
#Autowired
public LinkingController(OAuth2AuthorizedClientManager authorizedClientManager) {
this.authorizedClientManager = authorizedClientManager;
}
#GetMapping("/tenants/{tenantId}/externalsystem/{externalSystemName}/link")
public ModelAndView linking(#PathVariable Long tenantId, #PathVariable String externalSystemName, Authentication authentication,
HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
// ClientRegistrationRepository does not natively support multi-tenancy, so registrations are
// Prefixed with a tenantId
String registrationId = tenantId + "-" + externalSystemName;
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(registrationId)
.principal(authentication) // Confirmed this is the correct authentication from the JWT
.attributes(attrs -> {
attrs.put(HttpServletRequest.class.getName(), servletRequest);
attrs.put(HttpServletResponse.class.getName(), servletResponse);
})
.build();
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); // Confirmed with debugging that it is using the correct registration repository and finding the correct registration.
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
Map<String, Object> params = new HashMap<>();
params.put("token", accessToken.getTokenValue());
return new ModelAndView("rootView", params);
}
Security Config:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.oauth2Client()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.bearerTokenResolver(getCustomizedTokenResolver())
.jwt();
}
private BearerTokenResolver getCustomizedTokenResolver() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowFormEncodedBodyParameter(true);
resolver.setAllowUriQueryParameter(true);
return resolver;
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new BridgeClientRegistrationRepository(externalSystemService);
}
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(OAuth2AuthorizedClientRepository authorizedClientRepository) {
return new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository(), authorizedClientRepository);
}
}
UPDATE
My code originally defined the OAuth2AuthorizedClientManager as:
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(OAuth2AuthorizedClientRepository authorizedClientRepository) {
return new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository(), authorizedClientRepository);
}
It seems that without adding specific providers to the ClientProvider, it would not function. Corrected code is:
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
I'm trying to access a Spring App via Keycloak, but I always get a 401 Unauthorized error. Basically I have a chat module that works fine on its own, but once I add Keycloak I'm unable to access the app due to that 401 error.
I've followed about 3 tutorials that showed similar things to what I've done, and I still have no idea what I've done wrong.
Here's my app's config :
keycloak:
enabled: true
realm: myReal
resource: myReal-api
public-client: true
bearer-only: true
auth-server-url: http://localhost:8080/auth
credentials:
secret: 82eXXXXX-3XXX-4XXX-XXX7-287aXXXXXXXX
principal-attribute: preferred_username
cors: true
From localhost:port/ I have a first interface (with no Keycloak safety) that has a link to my service, which is localhost:port/index/{topicName} . Now when I click on that link, I'm supposed to get the Keycloak authentication screen, but I get a 401 error instead.
I've checked the header of my request, adding a HttpServletRequest as a parameter to my displayMessage method, and I actually could display the access_token and the X-Auth-Token in my IDE's console. But it seems like when I follow that link, it sends the request without the token.
Here are my controller methods (my Controller class is annotated with #Controller:
#GetMapping(path = "/")
public String index() {
return "external";
}
#GetMapping(path = "/index/{topicName}",
produces = MediaType.APPLICATION_JSON_VALUE)
public String displayMessages(Model model,
#PathVariable String topicName) {
//HttpHeaders headers = new HttpHeaders();
//headers.set("Authorization", request.getHeader("Authorization"));
//header = request.getHeader("Authorization");
//System.out.println(" T O K E N "+request.getHeader("X-Auth-Token"));
projectServiceImpl.findByName(topicName);
List<Message> messages = messageServiceImpl.findAllMessagesByProjectName(topicName);
model.addAttribute("topic", topicName);
model.addAttribute("message",messages);
return "index";
}
My Keycloak config file is inspired from the tuto's I've read, so there might be a mistake in there that I don't know about (not sure what the difference between methods access and hasRole is) :
#Configuration
#ComponentScan(
basePackageClasses = KeycloakSecurityComponents.class,
excludeFilters = #ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
#EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Bean
public HttpSessionIdResolver httpSessionIdResolver() { //replace HttpSessionStrategy
return HeaderHttpSessionIdResolver.xAuthToken();
}
//Registers the KeycloakAuthenticationProvider with the authentication manager.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
try {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
grantedAuthorityMapper.setConvertToUpperCase(true);
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider());
} catch(Exception ex) {
logger.error("SecurityConfig.configureGlobal: " + ex);
}
/*try {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}catch(Exception ex){
logger.error("SecurityConfig.configureGlobal: " +ex);
}*/
}
//Load Keycloak properties from service config-file
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
//Defines the session authentication strategy.
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
//Public or Confidential application keycloak/OpenID Connect client
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
//Bearer mode only keycloak/OpenID Connect client without keycloak session -> stateless behavior
//return new NullAuthenticatedSessionStrategy();
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http.authorizeRequests()
//BEGIN
//USER -done to be tested
.antMatchers(HttpMethod.GET,"/index**").access("hasAuthority('ADMIN')")
.antMatchers(HttpMethod.GET,"/").access("hasAuthority('ADMIN')")
.antMatchers(HttpMethod.GET,"/").access("hasAnyAuthority('ADMIN','MANAGER','EXPERT','STANDARD')")
.anyRequest().authenticated()
.and()
.cors()
.and()
.csrf().disable()
//BEGIN Login/Logout
.formLogin()
.permitAll()//.successHandler(authenticationSuccessHandler) //
.and()
.logout()//.clearAuthentication(true) //Add .clearAuthentication(true) to logout()
//.logoutUrl("/custom-logout")
.addLogoutHandler(keycloakLogoutHandler())
//.addLogoutHandler(new LogoutHandlerImpl())
.clearAuthentication(true)
.invalidateHttpSession(true)
.permitAll();
//END Login/Logout
//BEGIN Session
http
.sessionManagement()
//.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) //BY default IF_REQUIRED
.maximumSessions(1)
.maxSessionsPreventsLogin(false) // if true generate an error when user login after reaching maximumSession (SessionAuthenticationStrategy rejected the authentication object / SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded)
//.expiredUrl("/auth/login")
.sessionRegistry(sessionRegistry());
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken accessToken() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return ((KeycloakSecurityContext) ((KeycloakAuthenticationToken) request.getUserPrincipal()).getCredentials()).getToken();
}
///BEGIN session
#Bean
public SessionRegistry sessionRegistry() {
SessionRegistry sessionRegistry = new SessionRegistryImpl();
return sessionRegistry;
}
#Bean
public RegisterSessionAuthenticationStrategy registerSessionAuthStr( ) {
return new RegisterSessionAuthenticationStrategy( sessionRegistry( ) );
}
// Register HttpSessionEventPublisher
#Bean
public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
I don't really know what else I should change to make it work, but I believe there has to be something wrong in there. But I think if I can have the Keycloak authentication screen when trying to access my service, this would be alright.
I received the same error, one thing to double check is that auth-server-url is the same for the server, and the client getting the token.
I.e. if one is the dns name, and one is the IP address, it will not authorize. (in my case I had localhost and 127.0.0.1 so authorization failed)
Server, src/main/resources/application.yml
Postman/client:
This solved my issue. I was using within a docker container and had to match both to host.docker.internal
When to access any route, I get redirected to my login page, including accessing my login page, which is expected. Now the problem is the login page does not even load. I have configured the controller method, hander, template configuration, and security configuration necessary to make this work, but all I get is the error below:
http://localhost:8080/login?errorMessage=Login+to+use+the+app
UserController.java
#Controller
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/signup")
public String signup(Model model) {
model.addAttribute("user", new User());
return "signup";
}
#PostMapping("/users")
public String createUser(User user) {
// only create user if it does not exist
if (userService.findByUsername(user.getUsername()) == null) {
user.setRoles(new String[] {"ROLE_USER"});
userService.save(user);
return "redirect:/login";
}
else {
return "redirect:/signup";
}
}
#GetMapping("/login")
public String login(Model model) {
model.addAttribute("user", new User());
return "login";
}
#GetMapping("/profile")
public String currentUserProfile(Model model) {
User currentUser = (User) model.asMap().get("currentUser");
model.addAttribute("user", currentUser);
model.addAttribute("authorized", true);
return "profile";
}
#GetMapping("/users/{id}")
public String userProfile(#PathVariable Long id, Model model) {
User queriedUser = userService.findOne(id);
model.addAttribute("user", queriedUser);
User currentUser = (User) model.asMap().get("currentUser");
if (currentUser != null && currentUser.isAdmin()) {
model.addAttribute("authorized", true);
}
return "profile";
}
}
UserHandler.java
#ControllerAdvice(basePackages = "com.valencra.recipes.web.controller")
public class UserHandler {
public static final String USERNAME_NOT_FOUND_ERR_MSG = "Unable to find username";
public static final String ACCESS_DENIED_ERR_MSG = "Login to use the app";
#Autowired
private UserService userService;
#ModelAttribute("authenticatedUser")
public User addAuthenticatedUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String username = authentication.getName();
User user = userService.findByUsername(username);
if (user != null) {
return user;
}
else {
throw new UsernameNotFoundException(USERNAME_NOT_FOUND_ERR_MSG);
}
}
else {
throw new AccessDeniedException(ACCESS_DENIED_ERR_MSG);
}
}
#ExceptionHandler(AccessDeniedException.class)
public String redirectIfUserNotAuthenticated(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", ACCESS_DENIED_ERR_MSG);
return "redirect:/login";
}
#ExceptionHandler(UsernameNotFoundException.class)
public String redirectIfUserNotFound(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", USERNAME_NOT_FOUND_ERR_MSG);
return "redirect:/login";
}
}
TemplateConfig.java
#Configuration
public class TemplateConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("LEGACYHTML5");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(templateResolver());
springTemplateEngine.addDialect(new SpringSecurityDialect());
return springTemplateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver() {
final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RecipesAppUserDetailsService recipesAppUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(recipesAppUserDetailsService)
.passwordEncoder(User.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/signup").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/login")
.and()
.csrf().disable();
http.headers().frameOptions().disable();
}
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> response.sendRedirect("/");
}
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) ->
response.sendRedirect("/login");
}
#Bean
public EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public Object getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
};
}
}
application.properties
# Package where our entities (models) are located
recipes.entity.package = com.valencra.recipes.model
# Details for our datasource
recipes.db.driver = org.h2.Driver
recipes.db.url = jdbc:h2:mem:recipes
# Hibernate properties
hibernate.dialect = org.hibernate.dialect.H2Dialect
hibernate.implicit_naming_strategy = org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
hibernate.format_sql = true
hibernate.show_sql = true
hibernate.hbm2ddl.auto = create-drop
spring.data.rest.basePath=/api/v1
resources directory
EDIT: See #lgaleazzi's answer, and the comments that follow. Essentially, removing the UserHandler class fixes it.
What does the stack trace say?
Looking at your UserHandler, you handle a null authenticated object, and a user you can find. You don't deal with what happens with an anonymous user. I think that's the issue.
If nobody is authenticated, you'll get an authentication object with an anonymous user. You can check that with the method isAuthenticated(). But you don't actually have to write this code, Spring Boot handles all this pretty well with its default configuration.
Try adding the following method to SecurityConfig:
#Override
public void configure(WebSecurity web) throws Exception {
// configuring here URLs for which security filters
// will be disabled (this is equivalent to using
// security="none")
web.ignoring().antMatchers("/login");
}
There you can specify URLs in your application for which authentication should not be applied (also useful for static resources).
In your case, /login is not excluded from authentication scope, so it causes one more redirection to /login, and you get a vicious circle.
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);