spring-boot error org.springframework.security.core.userdetails.User cannot be cast to in.cad.security.model.MyUserPrincipal class - spring

I am completely new to spring boot and trying to write Unit test cases but stuck completely and not able to understand how Authentication works.
Controller class
#PostMapping (path = "/createConcept")
#ApiOperation(value = "Composite object with conceptCO with added roles",
response = ConceptCO.class)
public ConceptCO createConcept(
#RequestBody final ConceptFormVO conceptFormVO,
#ApiIgnore Authentication authentication
) {
ConceptDTO conceptDTO = new ConceptDTO();
BeanUtils.copyProperties(conceptFormVO, conceptDTO,
AppUtils.getNullPropertyNames(conceptFormVO));
LOGGER.info("Input Config : ::{}", conceptFormVO);
List<UserPrincipalAttributes> cadenzPrincipalAttributesList = PrincipalUtil.getRoles(authentication);
String token = PrincipalUtil.getToken(authentication);
return conceptDelegate.createConcept(conceptDTO,cadenzPrincipalAttributesList,token);
}
PrincipalUtil.java
public final class PrincipalUtil {
public static final String BEARER_TOKEN = "Bearer ";
public static List<UserPrincipalAttributes> getRoles(final Authentication authentication) {
UserPrincipal user =
(UserPrincipal) authentication.getPrincipal();
return new ArrayList<>(user.getUserPrincipalAttributes());
}
public static String getToken(final Authentication authentication) {
UserPrincipal user =
(UserPrincipal) authentication.getPrincipal();
String finalToken = BEARER_TOKEN + user.getToken();
return finalToken;
}
}
UserPrincipal.java
public class UserPrincipal implements AuthenticatedPrincipal {
private String name;
private Set<UserPrincipalAttributes> userPrincipalAttributes;
private String token;
// getter & setters
}
UserPrincipalAttributes .java
public class UserPrincipalAttributes {
Set<String> columns;
Set<String> concepts;
String role;
// getter & setters
}
Below is my test function
private Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
private static final String BASE_URL = "/xyz";
#Before
public void setup() throws Exception {
mvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
#WithMockUser(username = "test_user1")
public void createConceptTest() throws Exception {
ConceptFormVO conceptFormVO = new ConceptFormVO();
conceptFormVO.setConceptExpression("payment_cat = ABC");
conceptFormVO.setConceptName("all_subsc_test");
RequestBuilder createConceptRequest = post(BASE_URL + "/createConcept",authentication)
.header("Authorization",
String.format("Bearer %s", accessToken))
.content(objectMapper.writeValueAsString(conceptFormVO))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON);
this.mvc
.perform(createConceptRequest)
.andExpect(status().isOk())
}
Running above test case gives me error
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to in.at.security.model.UserPrincipal
at in.at.security.util.PrincipalUtil.getRoles(PrincipalUtil.java)
Apologies for silly mistakes.

instead of passing Authentication u can directly inject AuthenticatedPrincipal refer below code let me know if it works,
import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public class SampleController {
#PostMapping (path = "/createConcept")
public SampleController createConcept(
#RequestBody final ConceptFormVO conceptFormVO,
#AuthenticationPrincipal OAuth2User principal
) {
Map<String, Object> principalDetails = principal.getAttributes();
Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
.....
}
}

Related

Spring Security 403 even with correct username and password , i can't authenticate

I'm trying to build a spring boot rest API with JWT role-based authentication, I'm stuck at the login part in spring security.
I'm currently using spring boot, spring data JPA (hibernate under the hood ), and Oracle 11g database.
All the tables get created and I can sign up but can't login.
WebSecurityConfig.java
import org.springframework.context.annotation.*;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST,"/users/**").permitAll()
.antMatchers("/roles").hasAnyAuthority("ADMIN")
.anyRequest().authenticated()
.and().addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
UserDetails.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public class UserDetails implements org.springframework.security.core.userdetails.UserDetails {
private User user;
#Autowired
private UsersRepository usersRepository;
public UserDetails(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
public UserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
}
UserDetailsServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UsersRepository usersRepository;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = usersRepository.getUserByUsername(username);
System.out.println("Found user in repo : "+user.getUsername()+" "+user.getPassword()+" "+user.getRoles());
if (user == null) {
throw new UsernameNotFoundException("Could not find user");
}
return new UserDetails(user);
}
}
JWTAuthenticationFilter.java
import com.auth0.jwt.JWT;
import com.bte.ifrs_server.entities.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import static com.auth0.jwt.algorithms.Algorithm.HMAC512;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
System.out.println("Attempting authentication");
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
System.out.println("Successfull Auth !!");
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
//Printing the access token into the response
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
//Creating access token object to return it as a response
AccessToken accessToken=new AccessToken(HEADER_STRING,TOKEN_PREFIX,token);
//Set the access token as a JSON response body
Gson gson = new Gson();
String access_token=gson.toJson(accessToken);
out.print(access_token);
out.flush();
//Adding the access token to response header
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
AccessToken.java
public class AccessToken {
String header,prefix,value;
public AccessToken(String header, String prefix, String value) {
this.header = header;
this.prefix = prefix;
this.value = value;
}
}
SecurityConstants.java
import java.util.Arrays;
import java.util.List;
public class SecurityConstants {
public static final String SECRET = "SecretKeyToGenJWTs";
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/users/sign-up";
public static final List<String> PUBLIC_ROUTES = Arrays.asList("/users/sign-up" , "/users/login" , "/roles/**");
}
Role.java
import javax.persistence.*;
#Entity
#Table(name = "roles")
public class Role {
#Id
#Column(name = "role_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
#SequenceGenerator(name="id_generator", sequenceName = "role_id_sequence",allocationSize = 1)
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
User.java
import java.util.*;
import javax.persistence.*;
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
#SequenceGenerator(name="id_generator", sequenceName = "user_id_sequence",allocationSize = 1)
private Long id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
and the main app:
IfrsServerApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#EnableJpaRepositories
#SpringBootApplication
public class IfrsServerApplication {
public static void main(String[] args) {
SpringApplication.run(IfrsServerApplication.class, args);
}
}
The code compiles and the server runs I can signup but authentication returns 403 after attempting to login ('/login').
Any Help will be appreciated. Thanks in advance.
You've shared quite a bit of code, so there may be other issues here, but one that I'll point out is that in your JWTAuthorizationFilter, you are not granting any authorities to the user:
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
The last parameter is what authorities the user has.
Configurations like:
.antMatchers("/roles").hasAnyAuthority("ADMIN")
will always return a 403 in that case.
The first solution I'd recommend is using Spring Security's built-in support for JWTs instead of rolling your own. There's a JWT login sample that looks quite similar to what you are trying to achieve.
Alternatively, you can try changing how you are calling that constructor so that you grant a list of authorities (like new SimpleGrantedAuthority("ADMIN")). The downside here is that you'll have a lot more code to maintain.

JHipster: Receive 401 Unauthorized when testing microservices

I generated simple microservices application with Jhipster I wrote simple controller like hello world,
When I am trying to test through test method it is always giving Unauthorized error and the test fails.
Controller:
#RestController
#RequestMapping("/api")
public class TestController{
#GetMapping("/test/{Id}")
public String TestGetData(#PathVariable int Id) {
return "Here is your data!";
}
}
Testclass:
#SpringBootTest(classes = HerstellerController.class)
#AutoConfigureMockMvc
public class TestIT {
#Autowired
private MockMvc mockMvc;
private static final long ONE_MINUTE = 60000;
private String token;
private Key key;
private TokenProvider tokenProvider;
#BeforeEach
public void setup() {
tokenProvider = new TokenProvider( new JHipsterProperties());
key = Keys.hmacShaKeyFor(Decoders.BASE64
.decode("xxxx"));
ReflectionTestUtils.setField(tokenProvider, "key", key);
ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE);
}
#Test
public void TestData() throws Exception {
token=tokenProvider.createToken(createAuthentication(),false);
String id="1";
String expData = "Here is your data!";
String result = mockMvc.perform(get("/api/test/"+ id)
.header("Authorization","Bearer " + token))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
System.out.println("\nResult:\n"+result);
}
private Authentication createAuthentication() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN));
return new UsernamePasswordAuthenticationToken("admin", "admin", authorities);
}
Changed the securityconfig also like this
.antMatchers("/api/**").permitAll()
.antMatchers("/api/**").anonymous()
I added authentication to securityContext in setup of Testclass!, It works!
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(createAuthentication());
SecurityContextHolder.setContext(securityContext);

Roles not working properly in Spring security [duplicate]

This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 3 years ago.
I am working on a spring security based web application in which I want to limit the items on the side bar based upon whether the logged in user has role ADMIN or USER. As far as authentication is concerned, everything is working fine but roles are not working as expected.
Following this post
For example -
<security:authorize access="hasRole('ADMIN')">
<li class=" nav-item"><a href="<c:url value = "/mapview/list"/>"><i
class="fa fa-map-marker"></i><span class="menu-title" data-i18n="">MapView</span></a>
</li>
</security:authorize>
The above element never gets visible even though I log in as - ADMIN.
Can someone please help me here in understanding what is going wrong.
Security Config
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests().antMatchers("/login", "/resource/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login").usernameParameter("username").passwordParameter("password").permitAll()
.loginProcessingUrl("/doLogin").successForwardUrl("/postLogin").failureUrl("/loginFailed").and()
.logout().logoutUrl("/doLogout").logoutSuccessUrl("/logout").permitAll().and().csrf().disable();
}
}
UserDetailsImpl
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private Collection<SimpleGrantedAuthority> authorities;
private String username;
private String password;
private Boolean enabled = true;
public void setAuthorities(Collection<SimpleGrantedAuthority> authorities) {
this.authorities = authorities;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
UserDetailsService
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
private Converter<User, UserDetails> userUserDetailsConverter;
#Autowired
#Qualifier(value = "userToUserDetails")
public void setUserUserDetailsConverter(Converter<User, UserDetails> userUserDetailsConverter) {
this.userUserDetailsConverter = userUserDetailsConverter;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userUserDetailsConverter.convert(userService.findByUserName(username));
}
}
UserToUserDetails
#Component
public class UserToUserDetails implements Converter<User, UserDetails>{
#Override
public UserDetails convert(User user) {
UserDetailsImpl userDetails = new UserDetailsImpl();
if (user != null) {
userDetails.setUsername(user.getUsername());
userDetails.setPassword(user.getEncryptedPassword());
userDetails.setEnabled(user.getEnabled());
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
userDetails.setAuthorities(authorities);
}
return userDetails;
}
}
Controller
#SessionAttributes({ "currentUser" })
#Controller
public class HomeController {
//skipping other mappings
#RequestMapping(value = "/postLogin", method = RequestMethod.POST)
public String postLogin(Model model, HttpSession session) {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder
.getContext().getAuthentication();
validatePrinciple(authentication.getPrincipal());
String username = ((UserDetailsImpl) authentication.getPrincipal()).getUsername();
model.addAttribute("currentUser", username);
return "redirect:/dashboard";
}
}
User
#Entity
public class User extends Auditable<String> {
//skipping other details
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private Set<Role> roles = new HashSet<>();
}
Tables created in db-
user
role
user_role
EDIT 1
Just added this mapping inside my controller and Once I login, if i hit /test on browser, both the booleans show false. I have no idea why the roles are not being set.. :-(
#GetMapping(value = "/test")
public void test(SecurityContextHolderAwareRequestWrapper request) {
boolean b = request.isUserInRole("ADMIN");
System.out.println("ROLE_ADMIN=" + b);
boolean c = request.isUserInRole("USER");
System.out.println("ROLE_USER=" + c);
}
EDIT 2
But, at the same time, below code shows role as ADMIN
public void test(SecurityContextHolderAwareRequestWrapper request) {
for (GrantedAuthority authority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
String userRole = authority.getAuthority();
System.out.println(userRole);
}
}
This looks like information is getting lost somewhere between retrieval from database and returning a response to the browser (obviously). Good thing you have a debugger, so I would start by tracing each step from where you access your database data, until you return that information. Follow every single step until you find where is it being lost, and post back when you have narrowed it down to a single place. If not maybe we can start looking at your html/script/template engine, but only after we are sure info is reaching browser.

Custom User Details and Custom Authentication provider are never called

I need some additional data in in the user details of authenticated users. So i wrote a custom details service and as a second approach a custom authentication provider to enrich the data in the user object. But the principal object in the security context stays a string instead of becoming the desired user object and when i'm setting breakpoints im my custom details service and authentication porvider it looks like this code is never used by spring albeit my customized classes are listed in springs authentication manager builder.
This is my custom user details service:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.HashSet;
import java.util.Set;
/*** #author schlund*/
public class CustomStudentDetailsService implements UserDetailsService {
private SecurityUserRepository securityUserRepository;
public CustomStudentDetailsService(SecurityUserRepository userSecurityRepository){
this.securityUserRepository=userSecurityRepository;
}
#Override
public SecurityUser loadUserByUsername(String kitID) throws UsernameNotFoundException {
try {
SecurityUser securityPerson = securityUserRepository.findByUsername(kitID);
if (securityPerson == null) {
return null;
}
return securityPerson;
}
catch (Exception e){
throw new UsernameNotFoundException("User not found");
}
}
private Set<GrantedAuthority> getAuthorities(SecurityUser securityPerson){
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(securityPerson.getRole());
authorities.add(grantedAuthority);
return authorities;
}
}
This is my custom authentication provider:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication ) throws AuthenticationException {
String password = authentication.getCredentials().toString().trim();
SecurityUser appUser = new SecurityUser();
return new UsernamePasswordAuthenticationToken(appUser, password, null);
}
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
This is my web security config:
package edu.kit.tm.cm.bamsg.bffweb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import edu.kit.tm.cm.bamsg.bffweb.iamservice.*;
#Configuration
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan("edu.kit.tm.cm.bamsg.bffweb.iamservice")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REALM = "bam";
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.and()
//endpoints without authentication
.authorizeRequests().antMatchers("/logged", "/userData").permitAll()
.and()
// default with authentication
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor(OAuth2ClientContext context, OAuth2ProtectedResourceDetails details) {
return new OAuth2FeignRequestInterceptor(context, details);
}
#Bean
BasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
BasicAuthenticationEntryPoint basicAuth = new BasicAuthenticationEntryPoint();
basicAuth.setRealmName(REALM);
return basicAuth;
}
}
And at least after authentication at the code line with the System.out.println the customized services should have been called, but unfortunatelly they are not. Breakpoints in the customized services have never been reached and the principal is still a string and not my customized user:
#ComponentScan("edu.kit.tm.cm.bamsg.bffweb.iamservice")
#RestController
#RequestMapping("/api/theses")
public class ThesisController {
#Autowired
private ThesisClient thesisClient;
#Autowired
private ThesisPersonLinker linker;
#Autowired
private ThesisPersonFilter filter;
#GetMapping
#PreAuthorize("hasRole('theses')")
public ResponseEntity<Collection<ThesisFrontendDTO>> findAllTheses() {
System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
The extended user class looks like that:
package edu.kit.tm.cm.bamsg.bffweb.iamservice;
import org.springframework.security.core.userdetails.User;
public class SecurityUser extends User{
String firstName;
String name;
String password;
private static final long serialVersionUID = 1L;
public SecurityUser() {
super("user", "none", null);
firstName = "Rainer";
name = "Schlund";
password = "meins";
}
public String getRole(){
return "Student";
}
}
The code contains some simplifications for testing like SecurityPerson always returning the same person, but i think that should not be a problem.
To address the problem of "principal object in the security context stays a string instead of becoming the desired user object" if you have gone through the Principal object it has getCreditantial() method returning object only , considering security user is principal object it is not providing enough information to become correct principal object.
Please take a look on UserDetailsPrincipal class for principal implementation :
public class UserDetailsPrincipal extends org.springframework.security.core.userdetails.User implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private Member user;
List<GrantedAuthority> authorities;
public UserDetailsPrincipal(Member user, List<GrantedAuthority> authorities ) {
super(user.getLogin(),user.getEncrytedPassword(),authorities);
this.authorities = authorities;
this.user = user;
}
// #Override
// public Collection<? extends GrantedAuthority> getAuthorities() {
// return this.authorities;
// }
#Override
public String getPassword() {
return user.getEncrytedPassword();
}
#Override
public String getUsername() {
return user.getLogin();
}
#Override
public boolean isAccountNonExpired() {
return !user.getIsExpired();
}
#Override
public boolean isAccountNonLocked() {
return !user.getIsLocked() || user.getIsLocked() == null;
}
#Override
public boolean isCredentialsNonExpired() {
return !user.getIsExpired() || user.getIsExpired() == null;
}
#Override
public boolean isEnabled() {
return user.getActive() == 1;
}
}
also used customAuthProvider like this :
#Slf4j
#Component("customAuthProvider")
#Transactional(readOnly = true,propagation = Propagation.REQUIRES_NEW)
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
#Qualifier("userDetailsServiceAdapter")
private UserDetailsServiceAdapter userDetailsService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
/* Member member = userRepository.findUserAccount(login); */
log.info("user for login inside custom auth service service : " + login);
if (!StringUtils.isEmpty(login) && !StringUtils.isEmpty(password)) {
try {
UserDetails userDetail = userDetailsService.loadUserByUsernameAndPassword(login, password);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetail,
userDetail.getPassword(), userDetail.getAuthorities());
token.setDetails(userDetail);
return token;
} catch (UsernameNotFoundException exception) {
return new UsernamePasswordAuthenticationToken(login, password, new ArrayList<>());
}
} else {
return new UsernamePasswordAuthenticationToken(login, password, new ArrayList<>());
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
If you want Spring security to use your Authentication provider you need to provide some entry point for providing auth credentials. Here is example of WebSecuritConfig class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REALM = "realm";
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.and()
// default with authentication
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint());
}
#Bean
BasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
BasicAuthenticationEntryPoint basicAuth = new BasicAuthenticationEntryPoint();
basicAuth.setRealmName(REALM);
return basicAuth;
}
}
And you need to change SecurityUser constructor, because you cannot pass null authorities to super constructor:
public SecurityUser() {
super("user", "none", new ArrayList<>());
firstName = "Rainer";
name = "Schlund";
password = "meins";
}
When you provide Authentication provider, UserDetailsService is not used. So you need to use it in auth provider.

How to retrieve scopes from OAuth token within Spring boot SSO + zuul

I am trying to make a simple API gateway using Spring boot SSO + Zuul. I need to translate OAuth scopes into headers which will be further used by some other backend service to do RBAC based on headers.
I am using this CustomOAuth2TokenRelayFilter that will basically set headers before sending to the backend. My issue is how do I get scopes from the current token. The class OAuth2AuthenticationDetails does provide the token value but it doesnt provide the scopes.
I am not sure about how to obtain the scopes in there.
Below is the Custom Zuul Filter which is mostly taken from
https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/proxy/OAuth2TokenRelayFilter.java
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
#Component
public class CustomOAuth2TokenRelayFilter extends ZuulFilter {
private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);
private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
private static final String TOKEN_TYPE = "TOKEN_TYPE";
private OAuth2RestOperations restTemplate;
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public int filterOrder() {
return 1;
}
#Override
public String filterType() {
return "pre";
}
#Override
public boolean shouldFilter() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof OAuth2Authentication) {
Object details = auth.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
RequestContext ctx = RequestContext.getCurrentContext();
LOGGER.debug ("role " + auth.getAuthorities());
LOGGER.debug("scope", ctx.get("scope")); // How do I obtain the scope ??
ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
return true;
}
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
return null;
}
private String getAccessToken(RequestContext ctx) {
String value = (String) ctx.get(ACCESS_TOKEN);
if (restTemplate != null) {
// In case it needs to be refreshed
OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
.getContext().getAuthentication();
if (restTemplate.getResource().getClientId()
.equals(auth.getOAuth2Request().getClientId())) {
try {
value = restTemplate.getAccessToken().getValue();
}
catch (Exception e) {
// Quite possibly a UserRedirectRequiredException, but the caller
// probably doesn't know how to handle it, otherwise they wouldn't be
// using this filter, so we rethrow as an authentication exception
throw new BadCredentialsException("Cannot obtain valid access token");
}
}
}
return value;
}
}
You could inject the OAuth2ClientContext into your filter, and use oAuth2ClientContext.getAccessToken().getScope() to retrieve the scopes.
OAuth2ClientContext is a session-scoped bean containing the current access token and preserved state.
So if we apply that to your example, it would look like this:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
#Component
public class CustomOAuth2TokenRelayFilter extends ZuulFilter {
private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);
private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
private static final String TOKEN_TYPE = "TOKEN_TYPE";
private OAuth2RestOperations restTemplate;
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public int filterOrder() {
return 1;
}
#Override
public String filterType() {
return "pre";
}
#Override
public boolean shouldFilter() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof OAuth2Authentication) {
Object details = auth.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
RequestContext ctx = RequestContext.getCurrentContext();
LOGGER.debug ("role " + auth.getAuthorities());
LOGGER.debug("scope" + oAuth2ClientContext.getAccessToken().getScope());
ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
return true;
}
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
return null;
}
private String getAccessToken(RequestContext ctx) {
String value = (String) ctx.get(ACCESS_TOKEN);
if (restTemplate != null) {
// In case it needs to be refreshed
OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
.getContext().getAuthentication();
if (restTemplate.getResource().getClientId()
.equals(auth.getOAuth2Request().getClientId())) {
try {
value = restTemplate.getAccessToken().getValue();
}
catch (Exception e) {
// Quite possibly a UserRedirectRequiredException, but the caller
// probably doesn't know how to handle it, otherwise they wouldn't be
// using this filter, so we rethrow as an authentication exception
throw new BadCredentialsException("Cannot obtain valid access token");
}
}
}
return value;
}
}
You can retrieve scopes from OAuth2 token with SecurityContextHolder and OAuth2Authentication
private static Set<String> getOAuthTokenScopes() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2Authentication oAuth2Authentication;
if (authentication instanceof OAuth2Authentication) {
oAuth2Authentication = (OAuth2Authentication) authentication;
} else {
throw new IllegalStateException("Authentication not supported!");
}
return oAuth2Authentication.getOAuth2Request().getScope();
}

Resources