Spring boot how make a user role managing with jwt - java

I'm writing a RESTful api with spring boot.
I'm using spring boot, jersey, mongo db, swagger, spring boot security and jwt.
I have written the models, the repositories for the requests to the DB. Now I have integrated the Security and jwt token.
Now I need to discretize the role of the users, because a user cant call a route that need an admin priviledges.
I have a route for login, it's return a token. This is the code of my SecurityConfig
...
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
UserRepository userRepository;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api/swagger.json").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers("/api/*").authenticated()
.and()
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager(), userRepository),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
I written the JWTLoginFilter that return me the token when user makes login
...
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
Credential creds = new ObjectMapper().readValue(req.getInputStream(), Credential.class);
User user = userRepository.login(creds);
if (user == null)
throw new BadCredentialsException("");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword()
);
return token;
}
...
I want insert this on my endpoint class on method
#PreAuthorize("hasRole('ROLE_ADMIN')")
this is a part of an endpoint
....
#Component
#Path("story")
#Api(value = "Story", produces = "application/json")
public class StoryEndpoint {
private static final Logger LOGGER = LoggerFactory.getLogger(StoryEndpoint.class);
#Autowired
StoryRepository storyRepository;
#GET
#Path("/")
#Produces(MediaType.APPLICATION_JSON)
#PreAuthorize("hasRole('ROLE_ADMIN')") <--- I want insert here
#ApiOperation(value = "Get All Story", response = Story.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "hello resource found"),
#ApiResponse(code = 404, message = "Given admin user not found")
})
public Response getAllStory(){
Iterable<Story> stories = storyRepository.findAll();
LOGGER.info("getAllStory");
return (stories!=null) ? Response.ok(stories).build() : Response.ok(ResponseErrorGenerator.generate(Response.Status.NOT_FOUND)).status(Response.Status.NOT_FOUND).build();
}
....
How I can make a mechanism for assign to user the role and how i can pass the role in token and discretize on route the role of user?

You need to store user roles inside JWT token as additional claims, extract them after token validation and pass as 'authorities' for principal:
Collection<? extends GrantedAuthority> authorities
= Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority))
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "",
authorities);
UsernamePasswordAuthenticationToken t
= new UsernamePasswordAuthenticationToken(principal, "", authorities);

First need to add the roles inside the JWT. For that you can add as Claim in the JWT Generator class.
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
Set<String> Userroles = new HashSet<>();
User user = userRepository.findByUsername(userDetails.getUsername());
for(Role role:user.getRoles()){
Userroles.add(role.getName());
}
claims.put("Roles",Userroles.toArray());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
In the user model class need to include the roles in Set or any other data structure.
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "USER_ROLES", joinColumns = {
#JoinColumn(name = "USER_ID") }, inverseJoinColumns = {
#JoinColumn(name = "ROLE_ID") })
private Set<Role> roles;
In the Repository need to have a method like below.
User findByUsername(String username);
Please check this Github Repo(https://github.com/Senthuran100/SpringBoot_JWT) for your reference.

you should add role into Token and for example you can refer following link:-
http://www.svlada.com/jwt-token-authentication-with-spring-boot/

Related

Spring Security JWT Authorization returning 401 on endpoints

I am able to register a user then login with those credentials and receive a valid JWT token with userId, username and the roles of a user.
Here is a screenshot of a token with 2 roles (USER and ADMIN)
jwt.io result
I inserted 2 roles (USER and ADMIN) into my roles table manually so that each user created will automatically be assigned a ROLE_USER once created.
The problem is, now that I want to access URLs based on the roles a user has, I am getting 401 unauthorized on endpoints that I tried to grant access to.
This is a postman response from a User who has a role of User and Admin, but I'm getting this 401 still.
postman response
Here are the important classes
My Role entity has a name and description fields
My Repository interfaces have a findByXXX method
User.java
#Entity
#Table(name="user")
public class User implements UserDetails{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
#NotBlank(message = "Username is required")
private String username;
#NotBlank(message = "Password is required")
private String password;
#Transient
private String confirmPassword;
private boolean enabled = true;
#JsonFormat(pattern = "yyyy-mm-dd")
private Date createdAt;
// I want to load all the roles of users + the user itself once requested for
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name="user_roles",
joinColumns = {
#JoinColumn(name="user_id")
},
inverseJoinColumns = {
#JoinColumn(name="role_id")
})
private Set<Role> roles = new HashSet<>();
// Constructor
// Getters and setteres
#Override
#JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
#JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
#Override
#JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
#Override
#JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
};
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/users/register", "/api/users/login", "/api/users/all").permitAll()
.antMatchers("/api/users/user/**").hasRole("USER")
.antMatchers("/api/users/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
// handles the json exception response if details are incorrect with an appropriate message
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
httpSecurity.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
UserService.java
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private RoleService roleService;
public User saveUser(User user) {
Role userRole = roleService.findByRoleName("USER");
// Role adminRole = roleService.findByRoleName("ADMIN");
Set<Role> roles = new HashSet<>();
try {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setUsername(user.getUsername());
user.setConfirmPassword("");
roles.add(userRole);
user.setRoles(roles);
return userRepository.save(user);
} catch(Exception e) {
throw new UsernameAlreadyExistsException("User with username " + user.getUsername() + " already exists!");
}
}
public List<User> findAll() {
List<User> list = new ArrayList<>();
userRepository.findAll().iterator().forEachRemaining(list::add);
return list;
}
}
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User not found");
}
return user;
}
private Set getAuthority(User user) {
Set<SimpleGrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
});
return authorities;
}
}
JwtTokenProvider.java
#Component
public class JwtTokenProvider {
private final String SECRET = "SECRET";
// Generate the token
public String generateToken(Authentication authentication) {
User user = (User) authentication.getPrincipal();
Date now = new Date(System.currentTimeMillis());
Date expiryDate = new Date(now.getTime() + 300_000);
String userId = Long.toString(user.getId());
// this is what holds the token
// add roles in claims
Map<String, Object> claims = new HashMap<>();
claims.put("id", (Long.toString(user.getId())));
claims.put("username", user.getUsername());
claims.put("roles", user.getRoles());
return Jwts.builder().setSubject(userId).setClaims(claims).setIssuedAt(now).setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
}
// Validate the token
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
System.out.println("Invalid JWT Signature");
} catch (MalformedJwtException ex) {
System.out.println("Invalid JWT Token");
} catch (ExpiredJwtException ex) {
System.out.println("Expired JWT Token");
} catch (UnsupportedJwtException ex) {
System.out.println("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
System.out.println("JWT claims string is empty");
}
return false;
}
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJWTFromRequest(httpServletRequest);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJWT(jwt);
User userDetails = (User) userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getJWTFromRequest(HttpServletRequest request) {
// Header Authorization: Bearer token
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
UserController.java
#RestController
#RequestMapping("/api/users")
#CrossOrigin
public class UserController {
private UserService userService;
private UserDetailsServiceImpl userDetailsService;
private UserValidator userValidator;
private ErrorValidationService errorValidationService;
private JwtTokenProvider tokenProvider;
private AuthenticationManager authenticationManager;
#Autowired
public UserController(UserService userService, UserDetailsServiceImpl userDetailsService, UserValidator userValidator, ErrorValidationService errorValidationService, JwtTokenProvider tokenProvider, AuthenticationManager authenticationManager) {
this.userService = userService;
this.userDetailsService = userDetailsService;
this.userValidator = userValidator;
this.errorValidationService = errorValidationService;
this.tokenProvider = tokenProvider;
this.authenticationManager = authenticationManager;
}
// I want to allow role based access to these URLs
#GetMapping("/all")
public String welcomeAll() {
return "Anyone can view this!";
}
#GetMapping("/admin")
public String adminPing(){
return "Only Admins Can view This";
}
#GetMapping("/user")
public String userPing(){
return "Any User Can view This";
}
// Get all users
#GetMapping("/userList")
public ResponseEntity<?> getAllUsers() {
return ResponseEntity.ok(userService.findAll());
}
#PostMapping("/register")
public ResponseEntity<?> register(#Valid #RequestBody User user, BindingResult result) {
userValidator.validate(user, result);
ResponseEntity<?> errorMap = errorValidationService.validationService(result);
if(errorMap != null) return errorMap;
User newUser = userService.saveUser(user);
return new ResponseEntity<User>(newUser, HttpStatus.CREATED);
}
#PostMapping("/login")
public ResponseEntity<?> login(#Valid #RequestBody LoginRequest loginRequest, BindingResult result) throws Exception {
System.out.println("Entering /login");
ResponseEntity<?> errorMap = errorValidationService.validationService(result);
if(errorMap != null) return errorMap;
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = "Bearer " + tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtLoginSuccessResponse(true, jwt));
}
}
If anyone can let me know why this is happening , and how I can go about changing and abstracting things in a much better way I would appreciate it a lot. Since there's so many different implementations online I'm not sure what i'm doing wrong.
Using hasAnyAuthority instead of hasRole and remove "ROLE_" from SimpleGrantedAuthority
Your code
.antMatchers("/api/users/user/**").hasRole("USER")
//...and...
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
Try this
.antMatchers("/api/users/user/**").hasAnyAuthority("USER")
//...and...
authorities.add(new SimpleGrantedAuthority(role.getName()));
But I dont't see where you use method UserDetailsServiceImpl.getAuthority? You put an empty list of authorities into UsernamePasswordAuthenticationToken in JwtAuthenticationFilter
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, Collections.emptyList());
You should put authorities in.

how to solve bad credentials spring Security?

I can't log in to my app as a user with the role admin or as a user with the role user. I always log in as a user with the role anonymous.
Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
#Autowired
UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/").authenticated()
.antMatchers("/rentAppPage/").hasRole("ADMIN")
.antMatchers("/addVehicle").hasRole("ADMIN")
.antMatchers("/getVehicle").hasRole("ADMIN")
.antMatchers("/removeVehicle").hasRole("ADMIN")
.antMatchers("/updateVehicle").hasRole("ADMIN")
.antMatchers("/allUser").hasRole("ADMIN")
.antMatchers("/resultGet").hasRole("ADMIN")
.antMatchers("/addUser").hasRole("ADMIN")
.antMatchers("/getUser").hasRole("ADMIN")
.antMatchers("/updateUser").hasRole("ADMIN")
.antMatchers("/removeUserById").hasRole("ADMIN")
.antMatchers("/price").hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allScooter").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allCar").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allMotorBike").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allBike").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
.antMatchers("/distance").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
.antMatchers("/user").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rent").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rent2").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/buy").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/buy2").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/thanks").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rentAppPage").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.and()
.formLogin()
.defaultSuccessUrl("/", true)
.and()
.logout()
.logoutSuccessUrl("/");
;
http.sessionManagement()
//.expiredUrl("/sessionExpired.html")
.invalidSessionUrl("/login.html");
}
}
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idUser;
private String username;
private String password;
private String name;
private String surname;
private String email;
private double latitude;
private double longitude;
private String role;
private String locationName;
}
#Slf4j
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
IUserDAO userDAO;
public UserDetailsServiceImpl(IUserDAO userDAO){
this.userDAO = userDAO;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDAO.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("User dont exist");
}
return new MyUserDetails(user);
}
}
#Repository
public class UserDAOImpl implements IUserDAO {
#Autowired
SessionFactory sessionFactory;
public UserDAOImpl(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
#Override
public void addUser(User user){
Session session = null;
Transaction tx = null;
try{
session = this.sessionFactory.openSession();
tx = session.beginTransaction();
session.save(user);
tx.commit();
}catch (HibernateException e){
if(tx != null)tx.rollback();
}finally {
session.close();
}
}
#Override
public User findByUsername(String username) {
Session session = this.sessionFactory.openSession();
User user =(User) session.createQuery("FROM pl.edu.wszib.model.User WHERE username = :username" )
.setParameter("username", username)
.uniqueResult();
session.close();
return user;
}
}
public class MyUserDetails implements UserDetails {
private User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
return Arrays.asList(authority);
}
#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 true;
}
}
2021-01-18 17:05:43.545 DEBUG 4256 --- [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#cd98cfcc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 127.0.0.1; SessionId: AD82C9600EFB66CF7C6F8A1BCCEEAE0D; Granted Authorities: ROLE_ANONYMOUS'
i have databse in MySQL with two users first role admin second role user my role in database is ROLE_ADMIN,ROLE_USER i my full code is here https://github.com/Conrado1212/SpringSecurityWhyCantWorkFine
can someone explain why i cant log in to app as user with role admin or user ?
Of course, there are many questions about encoder, md5, etc. But it's your choice even it's wrong )))
I run your code, if MD5 hash is correct in DB I can login. As I understand, the question is why the application does not store the state of logged in user?
Expectation:
1st request: call method (incognito) -> login -> call method (authorized)
Xth request: call method -> Spring Security checks -> call method (authorized)
Reality:
Xth request: always equals 1st
The cause that you don't change a state of Spring Security context. You call /login endpoint, check if the User is in DB, check his password and return success response. You should create an authorization and place it in the Spring Security context.
Simple example:
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.generateToken(authentication);
return token;
And in next requests use the token. How to implement the token provider there are many examples. It can be custom or better JWT.
If you don't want tokens (RESTful), you can use sessions in requests (RESTless) but it requires additional configuration.
Looked through the code you have posted on github and none of the code you posted in the above question is the problem. You problem is how you perform your login.
login.html
<form action="/authenticate" method="post" id="login" class="input-group">
<div th:text="`enter code here`${errorMessage}" id="error-message"></div>
<input type="text" class="input-field" placeholder="Enter username" name="username" th:field="*{userModel.username}" required>
<input type="password" class="input-field" placeholder="Enter password" name="password" th:field="*{userModel.password}"required>
<input type="checkbox" class="chech-box"><span class="span1">Remember password</span>
<button type="submit" class="submit-btn">Log in</button>
</form>
Here we se that the login form posts to the endpoint /authenticate. This is NOT the standard /login endpoint that spring security sets up for you automatically.
Since you are not using the the standard i find your custom endpoint.
LoginController.java
#RequestMapping(value = "/authenticate",method = RequestMethod.POST)
public String authenticateUser(#ModelAttribute("userModel")User user,Model model){
boolean authResult = this.authenticationService.authenticationUser(user);
if(authResult){
System.out.println("zalogowano !!");
return "rentAppPage";
} else {
model.addAttribute("errorMessage","zle dane!!!");
model.addAttribute("userModel",new User());
return "login";
}
}
Here we see that you pass the user object into a custom written function called authenticationUser. So if we look in that function we find this implementation.
AuthenticationService.java
#Override
public boolean authenticationUser(User user){
User userFromDb = this.userDAO.findByUsername(user.getUsername());
return userFromDb != null && userFromDb.getPassword().equals(DigestUtils.md5Hex(user.getPassword()));
}
All you do in this function, is to fetch the user from the database. Check if the users password matches, and then return a boolean.
If the boolean is true you return the next page.
This is NOT how spring security works. All of the above code is completely faulty, and its clear that no research has been done before asking here on stack overflow. How could the server know you have called that function before? it can't.
My answer to your question, is that your implementation is completely wrong, and i highly suggest you find a getting started guide to how form login works in spring security, because explaining how spring security works, can not be done in a simple answer.
All im going to do is to link you to the official Spring security FormLogin documentation, and you should start reading there.

Spring Security - 403 Forbidden for every role except ROLE_USER

I am creating an application using Role based restriction to certain REST endpoints.
I have a Roles table, user_roles table and a users table.
ROLE_USER is working fine, and users with that Role can successfully access their pages. Even if I swap it around so that only ROLE_USER can access /admin/**, it works.
However, when I try use ROLE_ADMIN on the admin page, it never works. It always redirects me to the 403 Forbidden page
Security Config:
#Bean
DaoAuthenticationProvider provider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceImpl);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/banking/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.GET, "/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403");
}
}
MyUserDetails:
public class MyUserDetails implements UserDetails {
private static final long serialVersionUID = -2067381432706760538L;
private User user;
public MyUserDetails(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_" + role.getName()));
}
return authorities;
}
UserDetailsServiceImpl:
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User not found");
}
return new MyUserDetails(user);
}
}
Would appreciate any help as I've been stuck on this for a while. Thanks!

How exactly works Spring Security configuration in this Spring Boot project?

I am not so into Spring Security. I am working on a Spring Boot project (implementing some REST web sercices) where someone else have implemented Spring Security to perform authentication. It seems works fine but I have some doubts about how it exactly works (the architecture).
So basically I have the following classes:
1) User that is my model class representing a user:
#Entity
#Table(name = "user",
uniqueConstraints = {
#UniqueConstraint(columnNames = {"email","username"})
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//#Pattern(regexp="\\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,4}\\b",flags = Pattern.Flag.CASE_INSENSITIVE)
#Column(name="email", unique=true,nullable=false)
private String email;
#NotNull
#Column(name="registration_date",nullable=false)
private Date registration_date;
#NotBlank
#Column(name="username",nullable = false,unique=true)
private String username;
#NotBlank
#Column(name="password",nullable = false)
private String password;
#Column(name="enabled", nullable = false)
private boolean enabled;
#ManyToMany
#JoinTable(name = "user_user_roles", joinColumns = {
#JoinColumn(name = "id_user", updatable = true) },
inverseJoinColumns = { #JoinColumn(name = "id_roles",
updatable = true)},
uniqueConstraints={#UniqueConstraint(columnNames = {"id_user","id_roles"})}
)
#Cascade({CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH,CascadeType.PERSIST,
CascadeType.SAVE_UPDATE})
private List<UserRole> userRoles;
// CONSTRUCTOR, GETTER AND SETTER METHODS
}
It is an hibernate class that provide the join with the user_user_roles database table containing the list of user rool associated to the specific user (I think the ROLE_ADMIN, ROLE_USER, etcetc)
2) Then I have CustomUserDetails class that extends User and implements Spring Security UserDetails interface.
So it means that will contain all the information related to a specific user (among which the role associated to this user) and implement all the methods declared in the UserDetails interface.
public class CustomUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(UserRole role : this.getUserRoles() ){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
authorities.add(grantedAuthority);
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String getUsername() {
return super.getUsername();
}
}
It seems to me that this class represent the bridge between Spring application and Spring Security (something like that carry the roles information related to a specific user, is it?).
In this class the information related to the list of roles of an user is contained into a collection of generic object that extends GrantedAuthority.
I think that the GrantedAuthority objects represents the roles of a user (infact are builded by role.getName() that is the string representing a role of the current user). Is it this reasoning correct?
Basically the prvious implementation only return the collection of GrantedAuthority related to a specific user, is it?
3) The CustomUserDetailsService class implementing Spring Security UserDetailsService interface:
#Transactional
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
#Autowired
private UserDAO userDao;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("No user present with username: "+username);
}else{
return new CustomUserDetails(user);
}
}
}
Basically this is a service that only implements the loadUserByUsername(String username)) method that do:
First retrieve the information related the user (a User object) including the list of his roles (List userRoles).
Then use this User object to build the previous CustomUserDetails used to retrieve the collection of the GrantedAuthority related to the user.
Is all these reasoning correct?
Then I also have this WebSecurityConfig that I think represent the Spring Security configuration:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
#EnableAutoConfiguration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
}
}
This is pretty obsucre for me. What exactly does?
From what I have understand it is enabling the REST web services security by ** #EnableWebSecurity**.
But why does the autowire of the UserDetailsService bean?
And what exactly foes this empty configure() method?
Your reasoning is correct.
Spring-security requires you to create a service which implements UserDetailsService. It expects service to have loadUserByUsername method which returns user object (which needs to implement Spring's User class). This instance of user is used to get authorities so that you can restrict access to certain urls. i.e. you can map url access to users with specific authorities.
In terms of the empty method configure(), it is used to for authentication and authorisation of the urls (mentioned above) and few more things. In fact, in terms of security, this is most flexible & powerful method available.
My project's config method looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/static/**").permitAll()
.mvcMatchers("/admin").access("hasAuthority('ROLE_ADMIN')")
.mvcMatchers("/employees").access("hasAuthority('ROLE_STAFF')")
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.logout().logoutSuccessUrl("/")
.and()
.headers().contentSecurityPolicy("default-src 'self' " +
"https://ajax.googleapis.com " +
"https://cdnjs.cloudfare.com " +
"style-src 'self' 'unsafe-inline' ");
}
The above example
Ensures all requests are authenticated and authorized.
Permits free access to static resources
Ensures url /admin is accessible only by users with role ADMIN
Setup basic login prompt for authentication
Sets logout url
Sets up cookie based CSRF token system
Sets content security policy
To understand more about all spring-security features, I highly recommend these resources.
excellent video presentations from Rob Winch (lead on Spring Security project)
Site with good examples
Sample code repo

Spring Security - Angular JS - Token based authentication with custom GenericFIlterBean

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.

Categories