i try to perform a login process with spring-boot, oauth2 and spring security. I implemented a custom userdetails service.
Here the code:
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserService userService;
#Autowired
public CustomUserDetailsService(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
return new UserRepositoryUserDetails(user);
}
private final static class UserRepositoryUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
private UserRepositoryUserDetails(User user) {
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles();
}
// another methods
#Override
public boolean isEnabled() { return super.isEnabled(); }
}
}
The user entity:
#Entity
#Table
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "id", columnDefinition = "VARCHAR(50)")
private String userUUId;
// another parametes
#Column(nullable = false, columnDefinition = "TINYINT DEFAULT false")
#Type(type = "org.hibernate.type.NumericBooleanType")
private boolean enabled;
public User() {
}
public User(User user) {
super();
this.userUUId = user.getUserUUId();
this.roles = user.getRoles();
this.name = user.getName();
this.email = user.getEmail();
this.enabled = isEnabled();
this.password = user.getPassword();
}
// ...
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
The security configuration:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And a part of an authorizationserver configuration:
#Configuration
#EnableAuthorizationServer
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean(name = "tokenStore")
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService);
}
Here the error log:
type=AUTHENTICATION_FAILURE, data={type=org.springframework.security.authentication.DisabledException, message=User is disabled}]
[2016-08-25 09:23:17.774] boot - 21158 INFO [http-nio-8443-exec-1] --- TokenEndpoint: Handling error: InvalidGrantException, User is disabled
[2016-08-25 09:23:17.832] boot - 21158 DEBUG [http-nio-8443-exec-1] --- OrderedRequestContextFilter: Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#6ea0e0af
[2016-08-25 09:23:17.837] boot - 21158 ERROR [http-nio-8443-exec-4] --- EndpointsAuthentification: org.springframework.web.client.HttpClientErrorException: 400 Bad Request
[2016-08-25 09:23:17.839] boot - 21158 DEBUG [http-nio-8443-exec-4] --- OrderedRequestContextFilter: Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#4afe7f7
[2016-08-25 09:23:17.840] boot - 21158 ERROR [http-nio-8443-exec-4] --- [dispatcherServlet]: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException
at com.x.server.controller.LoginController.login(LoginController.java:76)
But i am sure, the user account is enabled. A call of user.isEnabled return true, but the framework cannot detect it.
Any ideas?
Cheers
Probably the enabled field in database is null or false
The org.springframework.security.authentication.DisabledException is thrown when the isEnabled() method of the UserDetails returns false.
From your implementation, User user = userService.findByUsername(username); in the CustomUserDetailsService is fetching from database a user whose enabled property is false.
Find a way to change it to true.
in your UserDetailsImpl class, isEnabled must be return true;
#Override
public boolean isEnabled() {
return true;
}
Related
hi what i trying to achieve is to protect a url that only one role can access to it, when i try add .hasRole("USER"), still the other role can access it. Here is how i do it :
here is my controller :
#RestController
#RequestMapping("/couponapi")
public class CouponController {
#Autowired
CouponRepository couponRepository;
#PostMapping("/coupons")
public Coupon save(#RequestBody Coupon coupon) {
return couponRepository.save(coupon);
}
#GetMapping("/coupons/{code}")
public Coupon findByCode(#PathVariable("code") String code) {
return couponRepository.findByCode(code);
}
#GetMapping("/something")
public Coupon findByCodeX() {
return couponRepository.findByCode("SUPERSALE");
}
}
i want to protect #GetMapping("/something") only for ROLE_ADMIN, here is how my Spring Security Configuration looked like :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailServiceImpl userDetailService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/couponapi/coupons/**").hasRole("USER")
.antMatchers(HttpMethod.POST,"/couponapi/coupons/**").hasRole("USER")
.antMatchers("/couponapi/something").hasRole("ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
here is my role class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles implements GrantedAuthority {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
#Override
public String getAuthority() {
return null;
}
}
and here is my service that implements UserDetailsService class :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), users.getRoles());
}
}
and here is my database role data :
as you can see i have ROLE_USER and ROLE_ADMIN
and here is my joined database
** i just updated my question and i have answer of half of my issue, please read my answer below to see the latest issue
In spring security the most restrictive rules are defined first, therefore your configuration should look like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/**/something").hasRole("USER")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
i find the culprit here, but not entirely i still missing the functional of HttpMethod one. here is how i fixed the role, in my Role class i do a mistake that i trying to implements the GrantedAuthority class, that the thing that cause this trouble (without HttpMethod issue). and here is how i fixed it
first, delete the impements and turn the role into usual #Entity class :
#Data
#EqualsAndHashCode(of = "id")
#ToString(of = { "id" })
#Entity
public class Roles {
private static final long serialVersionUID = -7314956574144971210L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<Users> users;
}
then at class that implements UserDetailsService, add this code :
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
i cannot explain it in details, but i think List only need 1 string, that's string is a role name. then here is the full code :
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users = userRepository.findByEmail(s);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
users.getRoles().forEach(d -> {
grantedAuthorities.add(new SimpleGrantedAuthority(d.getName()));
});
if(users == null) {
throw new UsernameNotFoundException("Username Not Found");
}
return new User(users.getEmail(), users.getPassword(), grantedAuthorities);
}
}
if anyone can find out the fix for this line .antMatchers(HttpMethod.GET,"/**/something").hasRole("USER") which is i want to use HttpMethod to differentiate each method with the same url, if someone have the answer, i will accept your answer. waiting for it
thanks for your good presentation of your problem.
That’s what I underestood, you want to give access only to user with Admin role to this URL /couponapi/something and give access to this URL /couponapi/coupons/** for all authenticated users whatever their roles (ADMIN or USER)
Try use hasAuthority instead of hasRole and delete the first line http.httpBasic() , it worked for me.
So in the WebSecurityConfig.java file:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/couponapi/something").hasAuthority("ROLE_ADMIN")
.antMatchers("/**").authenticated()
.and().httpBasic().and().csrf().disable();
}
Then In the above section of code, you only give access to users with ADMIN authority to access this url /couponapi/something so user with USER authority can’t access it.
And since you have declared a password encoder bean, you can use it with the AuthenticationManagerBuilder
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
I am trying to test my Spring Application which consists of JWT auth with Junit and Mockmvc. Signup test is working as expected. But when trying to test login, StackOverflowError is displayed. Inside the JWTAuthenticationFilter.java, data is being successfully received. But after that error is displayed. Please help me out. Thanks!
Error:
java.lang.StackOverflowError
at org.mockito.internal.invocation.MatchersBinder.bindMatchers(MatchersBinder.java:25)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:59)
at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:35)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:63)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:49)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:110)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder$MockitoMock$310380589.encode(Unknown Source)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.prepareTimingAttackProtection(DaoAuthenticationProvider.java:142)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:106)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200)
at org.springframework.security.con
WebSecurity.java
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserControllerTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureJsonTesters
public class UserControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private JacksonTester<User> json;
#Autowired
private JacksonTester<CreateUserRequest> jsonN;
#MockBean
private UserRepository userRepository;
#MockBean
private BCryptPasswordEncoder encoder;
private CreateUserRequest r;
#Before
public void setup(){
r = new CreateUserRequest();
r.setUsername("ujjwal2102");
r.setPassword("ujjwal21");
r.setConfirmPassword("ujjwal21");
}
#Test
public void createUserTest() throws Exception{
signup();
}
#Test
public void loginUserTest() throws Exception{
signup();
login();
}
public void signup() throws Exception{
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/api/user/create"))
.content(jsonN.write(r).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id",is(0)))
.andExpect(jsonPath("$.username",is("ujjwal2102")));
}
public void login() throws Exception{
User user = new User();
user.setUsername("ujjwal2102");
user.setPassword("ujjwal21");
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/login"))
.content(json.write(user).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
}
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
private long id;
#Column(nullable = false, unique = true)
#JsonProperty
private String username;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cart_id", referencedColumnName = "id")
#JsonIgnore
private Cart cart;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Cart getCart() {
return cart;
}
public void setCart(Cart cart) {
this.cart = cart;
}
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;
}
}
JWTAuthenticationFilter.java
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 {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
System.out.println("USERNAME-----" + creds.getUsername());
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 {
String token = JWT.create()
.withSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.sign(HMAC512(SecurityConstants.SECRET.getBytes()));
System.out.println("TOKEN----" + token);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
}
}
I noticed one mistake that may or may not solved your issue, but may compromise your authentification process.
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>()) // with autorities, the user is authenticated
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword()) // without autorities, the user is not authenticated
The constructor with autorities is usually used by the AuthentificationManager after authentification is successful. The constructor without is used by the filter to pass to the AuthentificationManager.
Spring Security Javadoc
Looking at the log, the issue looks to be in your Spring Security Configuration that you didn't provide. You may have done a recursive call inside an authentification provider.
I'm starting to learn Spring Security now and I got with trouble. I wrote configuration classes, getting data from DB and so on, but in my webpage I see the message "User account is locked" and error parameter in url after signing in.
MessengerApplication.java
#SpringBootApplication
public class MessengerApplication {
public static void main(String[] args) {
SpringApplication.run(MessengerApplication.class, args);
}
}
MainPageController.java
#RestController
public class MainPageController {
#RequestMapping("/")
public ModelAndView greeting() {
Map<String, Object> model = new HashMap<>();
model.put("data", "world");
return new ModelAndView("main_page", model);
}
}
SecurityConfig.java
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserServiceImpl userService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
UserServiceImpl.java
#Service
public class UserServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public MyUserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findUserByName(s);
if (user == null)
throw new UsernameNotFoundException(s);
return new MyUserDetails(user);
}
}
UserRepositoryImpl.java
#Repository
public class UserRepositoryImpl implements UserRepository {
#Autowired
JdbcTemplate template;
#Override
public User findUserByName(String name) {
return template.queryForObject("select * from users where name = ?", rowMapper, name);
}
private RowMapper<User> rowMapper = new RowMapper<User>() {
#Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setPassword(resultSet.getString("password"));
user.setName(resultSet.getString("name"));
user.setId(resultSet.getLong("id"));
return user;
}
};
}
UserRepository.java
public interface UserRepository {
User findUserByName(String name);
}
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String password;
// get(), set()
}
MyUserDetails.java
public class MyUserDetails implements UserDetails {
private User user;
public MyUserDetails(User user) {
this.user = user;
}
// ...
}
The method is isAccountNonLocked, emphasis on non. You need to return true from this method in order to have an 'unlocked' account. Same thing with the method that pertains to 'expired', etc. In this case true means allow it, false means reject it.
I'm implementing a project using Spring security oauth2, everything works perfectly, now I want to start digging deeper beyond the basics. I want to check if the user making the request is the actual user owner of the resource, the end result would be for example:
/private/users/{uuid}/clients returns all clients for the specified user.
So my controller now looks like this:
#RestController
public class HomeController {
#Autowired
private UserService userService;
#GetMapping(value = "/")
public String index() {
return "Hello world";
}
#GetMapping(value = "/private")
public String privateTest(Principal principal) {
User user = userService.get(principal.getName());
return user.getUuid();
}
}
EDIT: The full security code (working) for a better explanation.
ResourceServerConfig
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/","/home","/register","/login").permitAll()
.antMatchers("/private/**").authenticated();
}
}
CustomUserDetails with getters and setters off course
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String uuid;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.uuid = user.getUuid();
this.authorities = translate(user.getRoles());
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
Main
#SpringBootApplication
public class DummyOauthApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(DummyOauthApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count() == 0) {
service.save(new User("user", "password", UUID.randomUUID().toString(), Arrays.asList(new Role("USER"), new Role("ACTUATOR"))));
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
So, using the way I've implemented. I can get the actual user but it implies a database query every time an endpoint is called. Getting the user and match with the user uuid.
I want to find another way that I can get the user and then compare if the uuid = user.getUuid()
Thanks in advance.
After some time and a lot of mistakes, I've managed to find a solution that I leave here. The CustomUserDetails can be seen in the question and from there you can easily get the uuid and match with the requested one.
public static CustomUserDetails getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
return (CustomUserDetails) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
EDIT: if you want to return the user you do something like this
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private User user;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.user = user;
this.authorities = translate(user.getRoles());
}
}
And then in a Utils or something,
public static User getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return userDetails.getUser();
}
}
throw new IllegalStateException("User not found!");
}
Thanks for all the effort.
Perhaps you could implement a custom AuthenticationProvider and store user details as Principal
Spring Security Authentication Provider
I got stuck with a problem regarding token based authentication using the spring security framework.
What Iam trying to achieve is to authenticate an angular js client with a webservice based on spring.
When the client logs in, I am generating a token which is passed along with every request made to the back end. So far this is working quite well.
The token is generated and returned to the client successfully. Passing the token for each request is also working. My problem is that my custom filter is authenticating the client successfully, but after going through the rest of the filter chain it is always returning a HTTP 403 and I have no clue where it is coming from.
The code is inspired by https://github.com/philipsorst/angular-rest-springsecurity and I tried to integrate it into my own app.
So here is my set up:
Java Spring Config Application.java:
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource("classpath:applicationContext.xml")
#Order(1)
public class Application {
static Logger log = Logger.getLogger(Application.class.getName());
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = SpringApplication.run(Application.class);
DBUtil dbUtil = context.getBean(DBUtil.class);
dbUtil.insertDestData();
}
}
#EnableWebMvcSecurity
#EnableWebSecurity(debug = true)
#ComponentScan
#Configuration
#EnableAutoConfiguration
#Order(2)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ConfigurableApplicationContext context;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(
SessionCreationPolicy.STATELESS);
String[] restEndpointsToSecure = { "api/recipe", "api/ingredient" };
//for (String endpoint : restEndpointsToSecure) {
http.authorizeRequests().antMatchers("/api/ingredient/create/")
.hasRole("USER");
//}
SecurityConfigurer<DefaultSecurityFilterChain, HttpSecurity> securityConfigurerAdapter = new XAuthTokenConfigurer(
userDetailsServiceBean(), authenticationManagerBean());
http.apply(securityConfigurerAdapter);
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder)
throws Exception {
UserDAO userDAO = context.getBean(UserDAO.class);
authManagerBuilder.userDetailsService(userDAO);
}
#Bean(name = "myAuthManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
The user entity implementing the UserDetail interface
#Entity
#Table(name = "RB_USER")
public class User extends BaseAuditEntity implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 3719799602561353931L;
public User() {
super();
}
#Column(name = "NAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#Column(name = "SECTOKEN")
private String secToken;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<UserRole> userRoles;
#Column(name = "IS_ACCOUNT_NON_LOCKED")
private boolean isAccountNonLocked;
#Column(name = "IS_ENABLED")
private boolean isEnabled;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<String> roles = new HashSet<String>();
for (UserRole userRole : getUserRoles()) {
roles.add(userRole.getRole().getName());
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
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 isAccountNonLocked;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return isEnabled;
}
public String getSecToken() {
return secToken;
}
public void setSecToken(String secToken) {
this.secToken = secToken;
}
public List<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRole(List<UserRole> userRoles) {
this.userRoles = userRoles;
}
public void setUserName(String name) {
this.userName = name;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public void setAccountNonLocked(boolean isAccountNonLocked) {
this.isAccountNonLocked = isAccountNonLocked;
}
}
The user dao implementing the user detail service interface
#Repository
#Transactional
public class UserDAOImpl extends GenericDAOImpl<User> implements UserDAO,
UserDetailsService {
public UserDAOImpl() {
super();
setClazz(User.class);
}
#Override
public User findByUserName(String userName) {
String queryString = "SELECT user FROM User user "
+ "LEFT JOIN FETCH user.userRoles userRoles "
+ "WHERE user.userName like ?1";
Query query = createQuery(queryString);
query.setParameter(1, userName);
return (User) query.getSingleResult();
}
#Override
public UserDetails loadUserByUsername(String username) {
String queryString = "SELECT user FROM User user "
+ "LEFT JOIN FETCH user.userRoles userRoles "
+ "WHERE user.userName like ?1";
Query query = createQuery(queryString);
query.setParameter(1, username);
return (User) query.getSingleResult();
}
}
My extended SecurityConfigurerAdapter applying the custom filter to the filter chain
public class XAuthTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private UserDetailsService detailsService;
private AuthenticationManager authenticationManager;
public XAuthTokenConfigurer(UserDetailsService detailsService, AuthenticationManager authenticationManager) {
this.detailsService = detailsService;
this.authenticationManager = authenticationManager;
}
#Override
public void configure(HttpSecurity http) throws Exception {
XAuthTokenFilter customFilter = new XAuthTokenFilter(detailsService, authenticationManager);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
And what is basically the important part the filter itself
public class XAuthTokenFilter extends GenericFilterBean {
protected Logger log = Logger.getLogger(this.getClass().getName());
private final UserDetailsService detailsService;
private final TokenUtils tokenUtils = new TokenUtils();
private final AuthenticationManager authenticationManager;
private String xAuthTokenHeaderName = "x-auth-token";
public XAuthTokenFilter(UserDetailsService userDetailsService, AuthenticationManager authenticationManager) {
this.detailsService = userDetailsService;
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String authToken = httpServletRequest
.getHeader(this.xAuthTokenHeaderName);
if (StringUtils.hasText(authToken)) {
String username = this.tokenUtils
.getUserNameFromToken(authToken);
UserDetails details = this.detailsService
.loadUserByUsername(username);
if (this.tokenUtils.validateToken(authToken, details)) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
details.getUsername(), details.getPassword(),
details.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
Authentication authentication = this.authenticationManager
.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("========================> " + SecurityContextHolder.getContext().getAuthentication().getName() + " , " + SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
}
}
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
As I mentioned above the login is working perfectly as the back end is returning a token and the angular js app is applying the token to each request.
Now when I am for example trying to add a new ingredient which is for the moment the only endpoint I am securing (for test purposes) I always get a 403 HTTP error.
Request
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Content-Length 15
Content-Type application/json;charset=utf-8
Cookie user=%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667294%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22password%22%3A%22123%22%2C%22secToken%22%3A%22Vincent%3A1402394417353%3A62c641b65418ceecb47aad47d5fc5378%22%2C%22userRoles%22%3A%5B%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667299%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22role%22%3A%7B%22id%22%3A1%2C%22version%22%3A0%2C%22createdAt%22%3A1402390667287%2C%22createdBy%22%3Anull%2C%22updatedAt%22%3Anull%2C%22updatedBy%22%3Anull%2C%22name%22%3A%22USER%22%7D%7D%5D%2C%22authorities%22%3A%5B%7B%22authority%22%3A%22USER%22%7D%5D%2C%22accountNonLocked%22%3Atrue%2C%22accountNonExpired%22%3Atrue%2C%22credentialsNonExpired%22%3Atrue%2C%22enabled%22%3Atrue%2C%22username%22%3A%22Vincent%22%7D
Host localhost:8080
Referer http://localhost:8080/
User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0
x-auth-token Vincent:1402394417353:62c641b65418ceecb47aad47d5fc5378
As you can see the token is passed along as expected and the log output states the the user Vincent was authenticated successfully
2014-06-10 11:00:56.201 INFO 4495 --- [nio-8080-exec-4] c.receiptbook.security.XAuthTokenFilter : ========================> Vincent , true
Now when the filter chain gets processed it is returning a 403 error in the end saying the resource cant be accessed
POST /api/ingredient/create/
403 Forbidden
localhost:8080
So I really appreciate any hint or tip to solve this problem, cause I am really running out of ideas how to track down this issue.
Regards,
Vincent
UPDATE:
While debugging the filter chain I came across the fact that the UsernamePasswordAuthenticationFilter is not even in the chain ...? The ExceptionTranslationFilter seems to react with a 403 HTTP error, but from the filter chain I am not able to say why he's reacting like that.
[0] WebAsyncManagerIntegrationFilter (id=75)
[1] SecurityContextPersistenceFilter (id=74)
[2] HeaderWriterFilter (id=71)
[3] LogoutFilter (id=83)
[4] XAuthTokenFilter (id=70)
[5] RequestCacheAwareFilter (id=116)
[6] SecurityContextHolderAwareRequestFilter (id=127)
[7] AnonymousAuthenticationFilter (id=130)
[8] SessionManagementFilter (id=133)
[9] ExceptionTranslationFilter (id=134)
[10] FilterSecurityInterceptor (id=138)
RESOLVED:
So in the end and after a few ours of debugging I finally resolved the issue. The only problem was my role naming. My roles we're just named "USER" or "ADMIN", but spring security expects the names to have the format "ROLE_USER" or "ROLE_ADMIN". This is for sure configurable, but also pretty bad documented.
Anyways I hope that this information could be usefull for someone else.