I have a spring rest web application where I am doing authentication with a single game token. I would like to add Google (or GitHub, Facebook, Telegram) account binding (oauth2) so that I can log in with oauth2, because it takes a long time to generate a token every time, how can this be implemented?
My code https://github.com/iNiceHero/VueAuthSpring
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true)
#EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/", "/api/auth/**", "/js/**", "/error**", "/login", "/api/user/**").permitAll()
.mvcMatchers("/api/test/**").permitAll()
.anyRequest().authenticated()
.and().logout().logoutSuccessUrl("/login")
.and().csrf().disable();
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http.sessionManagement()
.sessionFixation().migrateSession();
}
}
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
UserDetailsImpl.java
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private Collection<? extends GrantedAuthority> authorities;
public UserDetailsImpl(Long id, String username, Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.authorities = authorities;
}
public static UserDetailsImpl build(User user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());
return new UserDetailsImpl(
user.getId(),
user.getUsername(),
authorities);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return null;
}
public Long getId() {
return id;
}
#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 true;
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UserDetailsImpl user = (UserDetailsImpl) o;
return Objects.equals(id, user.id);
}
}
AuthController.java authenticateUser
if (!userRepository.existsByUsername(username)) {
User user;
if (userRepository.existsById(id)) {
user = userRepository.findById(id).orElseThrow();
user.setUsername(username);
} else {
user = new User(username, id);
}
Set<String> strRoles = loginRequest.getRole();
Set<Role> roles = new HashSet<>();
if (strRoles == null) {
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
} else {
strRoles.forEach(role -> {
switch (role) {
case "admin":
Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(adminRole);
break;
case "mod":
Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(modRole);
break;
default:
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
}
});
}
user.setEnabled(true);
user.setRoles(roles);
userRepository.save(user);
}
User user = userRepository.findById(id).orElseThrow();
UserDetailsImpl userDetails1 = UserDetailsImpl.build(user);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails1, null, userDetails1.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> rolesUser = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return ResponseEntity.ok(new UserResponse(
userDetails.getId(),
userDetails.getUsername(),
rolesUser));
Related
I am trying to implement a database authentication using custom UserDetails implementation. I have three roles, which are STUDENT, ADMIN and ADMINTRAINEE (these are enums) and some authorities provided for them , which I fetch from in-memory db (but I'm gonna switch to external one). This is web security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig {
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService userService;
#Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder,ApplicationUserService userService) {
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
#Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/courses", true)
.passwordParameter("password")
.usernameParameter("username")
.and()
.rememberMe()
.tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))
.key("example")
.rememberMeParameter("remember-me")
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.logoutSuccessUrl("/login"); // custom address to redirect after logout
return http.build();
}
// This is what I need to rewrite
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
// Is used to utilize a custom impl of UserDetailsService
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
}
This is what the implementation of the UserDetailsService looks like:
#Service
public class ApplicationUserService implements UserDetailsService {
private final ApplicationUserDao applicationUserDao;
#Autowired
public ApplicationUserService(#Qualifier("fake") ApplicationUserDao applicationUserDao) {
this.applicationUserDao = applicationUserDao;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return applicationUserDao
.selectApplicationUserByUsername(username)
.orElseThrow(() ->
new UsernameNotFoundException(String.format("Username %s not found", username)));
}
}
So it calls the selectApplicationUserByUsername() method which is here:
public interface ApplicationUserDao {
Optional<ApplicationUser> selectApplicationUserByUsername(String username);
}
And this is the implementation of that interface:
#Repository("fake")
public class FakeApplicationUserDaoService implements
ApplicationUserDao {
private final PasswordEncoder passwordEncoder;
#Autowired
public FakeApplicationUserDaoService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Override
public Optional<ApplicationUser> selectApplicationUserByUsername(String username) {
return getApplicationUsers().stream()
.filter(applicationUser -> username.equals(applicationUser.getUsername()))
.findFirst();
}
private List<ApplicationUser> getApplicationUsers() {
List<ApplicationUser> applicationUsers = Lists.newArrayList(
new ApplicationUser(
"annasmith",
passwordEncoder.encode("password"),
STUDENT.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"linda",
passwordEncoder.encode("password"),
ADMIN.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"tom",
passwordEncoder.encode("password"),
ADMINTRAINEE.getGrantedAuthorities(),
true,
true,
true,
true
)
);
return applicationUsers;
}
}
And this is the ApplicationUser class which is a custom subsitute to the UserDetails default implementation class Spring Security uses:
public class ApplicationUser implements UserDetails {
private final Set<? extends GrantedAuthority> grantedAuthorities;
private final String password;
private final String username;
private final boolean isAccountNonExpired;
private final boolean isAccountNonLocked;
private final boolean isCredentialsNonExpired;
private final boolean isEnabled;
public ApplicationUser(String password,
String username,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.password = password;
this.username = username;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
#Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
#Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
#Override
public boolean isEnabled() {
return isEnabled;
}
}
So these are the enums for roles and authorities I wrote (the purpose of these is only for creating roles and the authorities users have; STUDENT does not have any authorities):
public enum ApplicationUserRole {
STUDENT(Sets.newHashSet()), // Sets is a class from the external library Guava
ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, STUDENT_WRITE)),
ADMINTRAINEE(Sets.newHashSet(COURSE_READ, STUDENT_READ));
private final Set<ApplicationUserPermission> permissions;
ApplicationUserRole(Set<ApplicationUserPermission> permissions) {
this.permissions = permissions;
}
public Set<ApplicationUserPermission> getPermissions() {
return permissions;
}
public Set<SimpleGrantedAuthority> getGrantedAuthorities() {
Set<SimpleGrantedAuthority> permissions = getPermissions().stream()
.map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toSet());
permissions.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
return permissions;
}
}
This is the ApplicationUserPermission class:
public enum ApplicationUserPermission {
STUDENT_READ("student:read"),
STUDENT_WRITE("student:write"),
COURSE_READ("course:read"),
COURSE_WRITE("course:write");
private final String permission;
ApplicationUserPermission(String permission) {
this.permission = permission;
}
public String getPermission() {
return permission;
}
}
And PasswordConfig class:
#Configuration
public class PasswordConfig {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
The granted authorities then are used in a controller class for ADMIN and ADMINTRAINEE with the #PreAuthorize annotation.
So the problem I have is in ApplicationSecurityConfig. I don't know how to call the AuthenticationManangerBuilder to pass that daoAuthenticationProvider I have. In older versions of Spring Security, I could just override the configure method with AuthenticationManagerBuilder instance as an argument, but It's no longer the case since that abstract class is currently deprecated. So how do I rewrite the method? Or do I even have to do so? Pls any help is appreciated.
My mistake was about the order of credentials variables in ApplicationUser constructor.As it turned out it is very important.
What was the mistake:
public ApplicationUser(String password,
String username,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.password = password;
this.username = username;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
How it should be written:
public ApplicationUser(String username,
String password,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.username = username;
this.password = password;
this.grantedAuthorities = grantedAuthorities;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
If you are using Spring Boot version with WebSecutiryConfigurerAdapter being deprecated, that configure method is not needed. You just build a securityFilterChain with the first method, and then provide password encoder and set userDetailsService for your db authentication.
I am writing a Spring-MVC application using Spring Boot and Hibernate. The application has a User who has the role of Admin and Customer. I decided to add Spring Security JWT and did everything right. Now that I have everything ready, I want to get a TOKEN, I log in successfully, but in return I get 403 Forbidden. What have I done wrong? Did you configure configs incorrectly or did the roles incorrectly?
Configs:
#RequiredArgsConstructor
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Fields
//
private final JwtTokenProvider jwtTokenProvider;
//
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}
}
Controller:
#RequiredArgsConstructor
#RestController
#RequestMapping("/auth")
public class AuthenticationController {
// Fields
//
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
private final UserService userService;
//
// GET-Methods
//
//
#PostMapping("/login")
public ResponseEntity<Map<String, String>> login(#RequestBody AuthenticationRequestDTO requestDto) {
try {
String login = requestDto.getLogin();
authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(login, requestDto.getPassword()));
User user = userService.findByLogin(login);
String token = jwtTokenProvider.createToken(login, user.getRole());
Map<String, String> response = new HashMap<>();
response.put("login", login);
response.put("token", token);
return ResponseEntity.ok(response);
} catch (AuthenticationException e) {
throw new BadCredentialsException("Invalid login or password");
}
}
}
User class:
#Getter
#Entity
#NoArgsConstructor
#Table(name = "users")
public class User {
// Fields
//
#Id
#GeneratedValue
private Long id;
private String name;
#Column(name = "last_name")
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
#Enumerated(EnumType.STRING)
private Role role;
}
#RequiredArgsConstructor
#Component
public class JwtTokenProvider {
// Fields
//
private final UserDetailsService userDetailsService;
#Value("${jwt.token.secret}")
private String secret;
#Value("${jwt.token.expired}")
private Long validityInMilliSeconds;
//
// METHODS
//
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(8);
}
#PostConstruct
protected void init() {
secret = Base64.getEncoder().encodeToString(secret.getBytes());
}
/**
* Генерируем ТОКЕН
*
* #param login
* #param role
* #return ТОКЕН
*/
public String createToken(String login, Role role) {
Claims claims = Jwts.claims().setSubject(login);
claims.put("roles", getRoleName(role));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliSeconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getLogin(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getLogin(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is expired or invalid");
}
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer_")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
private String getRoleName(Role role) {
String roleName = role.name();
return roleName;
}
}
#RequiredArgsConstructor
public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
#Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
if (auth != null) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}
#RequiredArgsConstructor
#Service
#Slf4j
public class JwtUserDetailsService implements UserDetailsService {
private final UserService userService;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userService.findByLogin(login);
JwtUser jwtUser = JwtUserFactory.create(user);
log.info("IN loadUserByUsername - user with login: {} successfully loaded", login);
return jwtUser;
}
}
#AllArgsConstructor
#Getter
public class JwtUser implements UserDetails {
// Fields
//
private Long id;
private String name;
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
private Role role;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role));
}
#Override
public String getUsername() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
P.S. I will not throw all the code for JWT from the project here, but skip only the most important thing.
LOGS:
2020-08-10 16:36:27.343 INFO 7556 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 64 ms
2020-08-10 16:36:28.454 INFO 7556 --- [nio-8080-exec-2] r.c.security.JwtUserDetailsService : IN loadUserByUsername - user with login: art123 successfully loaded
I have Spring Boot REST application which uses JWT tokens for authorization. I want to get current logged user in controllers using #AuthenticationPrincipal annotation. But it always returns null if i return custom model from loadUserByUsername and auth stop working. My model implements UserDetails.
I tried to extend the org.springframework.security.core.userdetails.User but i get rid errors from JWTAuthenticationFilter that default constructor not exists (ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);)
Whats wrong?
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private UserRepository userRepository;
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ApplicationUser applicationUser = userRepository.findByUsername(username);
if (applicationUser == null) throw new UsernameNotFoundException(username);
return applicationUser;
}
}
ApplicationUser.java (model)
#Entity
#Table(name = "users")
public class ApplicationUser implements UserDetails {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true, nullable = false)
private String username;
#Column(unique = true, nullable = false)
private String email;
#Column(nullable = false)
private String password;
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
}
JWTAuthenticationFilter
public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(LOGIN_URL));
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
ApplicationUser creds = new ObjectMapper()
.readValue(req.getInputStream(), ApplicationUser.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 {
String token = Jwts.builder()
.setSubject(((ApplicationUser) auth.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter
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;
try {
user = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
} catch (SignatureException e) {
return null;
}
if (user != null) return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
return null;
}
return null;
}
}
In your case the #AuthenticationPrincipal will return a string with the username,
you can get the user by calling the repository in your controller and getting the user by the username or declaring the repository as a #Bean and do the folowing:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
//Get the repository
private UserRepository userRepository;
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;
try {
user = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
} catch (SignatureException e) {
return null;
}
//Get your user
UserEntity userEntity = this.userRepository.findByUsername(user);
if (user != null) {
//Seting in your AuthenticationPrincipal the user
return new UsernamePasswordAuthenticationToken(userEntity, null, new ArrayList<>());
}
return null;
}
return null;
}
}
I recently implemented a method to get the username or email from the JWT token in SpringBoot. Sharing the experience
private String getUserName() {
JwtAuthenticationToken authenticationToken = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) authenticationToken.getCredentials();
String email = (String) jwt.getClaims().get("email");
return email;
}
Check if you are using suitable annotation, because one of them is deprecated.
Documentation - deprecated!
Documentation - fine!
In addition, be aware to resolve username (String) as an argument, not User type:
Annotation that is used to resolve Authentication.getPrincipal() to a method argument.
Check this topic as well! It can help.
I don't know if it is good practice (I'm not considered 'pro' in Spring yet), but in my personal project I get token from HttpServletRequest object passed in controller parameter. Then I use JwtTokenUtil class, which have getUserFormToken(String token); method to resolve user/username. It looks like this:
Controller
#Autowired
TestService testService;
#Autowired
UserService userService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#RequestMapping(value="/test", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public List<Test> getTestsListByUserId(HttpServletRequest req){
String token = req.getHeader(HEADER_STRING).replace(TOKEN_PREFIX,"");
return testService.findByUserId(userService.findByUsername(jwtTokenUtil.getUsernameFromToken(token)));
}
JwtTokenUtil
#Component
public class JwtTokenUtil implements Serializable {
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(User user) {
return doGenerateToken(user.getUsername());
}
private String doGenerateToken(String subject) {
Claims claims = Jwts.claims().setSubject(subject);
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
return Jwts.builder()
.setClaims(claims)
.setIssuer("issuer")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
}
But I generally have different filters implementation according yo yours. If you are interested - I used this tutorial and implementation.
To retrieve a custom model i do next things:
Get model from Database and set it as Principal.
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user;
try {
user = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
} catch (SignatureException e) {
return null;
}
// Get user model
ApplicationUser userModel = userRepository.findByUsername(user);
// Set it
if (user != null && userModel != null) return new UsernamePasswordAuthenticationToken(userModel, null, new ArrayList<>());
return null;
}
return null;
}
Then in controller retrieve using #AuthenticationPrincipal annotation.
public ApplicationUser getCurrentUser(#AuthenticationPrincipal ApplicationUser user) {
return user;
}
If this is still actual, I have just answered similar question here
Main point is to take authorization token from header:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String token = request.getHeader("Authorization").split(" ")[1];
after that you can decode it and get parts which you need.
User.java
public class User implements Serializable{
#Id
#Size(min=5,max=15)
#Column(name="username", unique=true)
private String username;
#OneToMany(mappedBy="user")
private Collection<Role> roles;
public User(User user) {
this.username=user.username;
this.roles=user.getRoles();
}
}
Role.java
public class Role implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String role;
#ManyToOne
#JoinColumn(name="username")
private User user;
}
UserServiceImpl.java
public class UserServiceImpl implements UserServiceDetails {
private UserRepo userRepo;
#Autowired
public void setUserRepo(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user=userRepo.findUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("Could not find user " + username);
}
CustomUser customUser=new CustomUser(user);
return customUser;
}
public final static class CustomUser extends User implements UserDetails
{
public CustomUser(User user) {
super(user);
}
#SuppressWarnings("unchecked")
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities =
new ArrayList<GrantedAuthority>();
authorities.addAll((Collection<? extends GrantedAuthority>) getRoles());
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
}
}
SecurityCOnfig.java
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(userDetailsService);
}
}
Errors:
Role cannot be cast to org.springframework.security.core.GrantedAuthority
my roles table contain roles in the form string "user", "admin" etc
Any suggestions?
Thanks
As Denium says, casting won't work the way it's being done in your code. I do similar thing by looping through each role, like this:
authorities = new HashSet<GrantedAuthority>(roles.size());
for (String role : roles)
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
So I have class:
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private EmployeeRepository employeeRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Employee employee = employeeRepository.readEmployee(s);
if(employee != null) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(employee.getRole().name()));
//how to save employee login and user id????
return new User(employee.getLogin(), employee.getPassword(),true, true, true, true, authorities)
}else {
throw new UsernameNotFoundException("Can't locate employee '" + s + "'");
}
}
}
And class in which I use the stored data:
public class CurrentEmployeeParam {
public static Long getCurrentEmployeeId(){
// how to get saved employee login and id???
String employeeId = //something
return Long.parseLong(employeeId);
}
So how to saved employee info and get employee info from any point in the program?
if you are using Spring MVC you can use session scoped beans
For example, can use:
public class AuthenticatedEmployee extends org.springframework.security.core.userdetails.User {
private Long employeeId;
private Long companyId;
public AuthenticatedEmployee(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, Collection<? extends GrantedAuthority> authorities)
throws IllegalArgumentException {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, true, authorities);
}
// setters and getters
}
UserDetails class:
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private EmployeeRepository employeeRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Employee employee = employeeRepository.readEmployee(s);
if(employee != null) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(employee.getRole().name()));
AuthenticatedEmployee user = new AuthenticatedEmployee(employee.getLogin(), employee.getPassword(), true, true, true, authorities);
user.setEmployeeId(employee.getId());
user.setCompanyId(employee.getCompany().getId());
return user;
}else {
throw new UsernameNotFoundException("Can't locate employee '" + s + "'");
}
}
}
And get employee info:
public class CurrentEmployeeParam {
public static Long getCurrentCompanyId() {
return getAuthenticatedEmployee().getCompanyId();
}
public static Long getCurrentEmployeeId() {
return getAuthenticatedEmployee().getEmployeeId();
}
private static AuthenticatedEmployee getAuthenticatedEmployee() {
return (AuthenticatedEmployee) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}