I am trying to get the current logged in username using Spring's security but the Principal object returns null.
This is my REST controller method:
#RequestMapping("/getCurrentUser")
public User getCurrentUser(Principal principal) {
String username = principal.getName();
User user = new User();
if (null != username) {
user = userService.findByUsername(username);
}
return user;
}
NB: I am running Spring boot 1.5.13 and spring security 4.2.6
This is my security config class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private Environment env;
#Autowired
private UserSecurityService userSecurityService;
private BCryptPasswordEncoder passwordEncoder() {
return SecurityUtility.passwordEncoder();
}
private static final String[] PUBLIC_MATCHERS = {
"/css/**",
"/js/**",
"/image/**",
"/book/**",
"/user/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().httpBasic().and().authorizeRequests()
.antMatchers(PUBLIC_MATCHERS).permitAll().anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
This is my user security service class:
#Service
public class UserSecurityService implements UserDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(UserSecurityService.class);
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(null == user) {
LOG.warn("Username {} not found", username);
throw new UsernameNotFoundException("Username "+username+" not found");
}
return user;
}
}
this is my user class:
#Entity
public class User implements UserDetails, Serializable {
private static final long serialVersionUID = 902783495L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="Id", nullable=false, updatable = false)
private Long id;
private String username;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled = true;
#OneToMany(mappedBy = "user", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles = new HashSet<>();
}
There are multiple ways to do this.
Using SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Using Principal from a Controller
#RequestMapping(value = "/myusername", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
From HttpServletRequest
#RequestMapping(value = "/myusername", method = RequestMethod.GET)
#ResponseBody
public String getUsername(HttpServletRequest req) {
return req.getUserPrincipal.getName();
}
Assuming your User class implements UserDetails, you can get the User from SecurityContextHolder without dependency injection.
public User getCurrentUser() {
return ((User) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal());
}
The previous answer should work fine,
if using spring web MVC controllers you can also have it injected into your controllers automatically out of the box using springs default method argument resolvers (org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver)
A controller can do:
#RestController
public class AcceptConnectionController {
#PostMapping(value = "/")
public void controllerMethod(#AuthenticationPrincipal final MyPrincipal user) {
//...
}
}
In the example above, MyPrincipal extends org.springframework.security.authentication.AbstractAuthenticationToken
You can then pass this principal to your service layer.
As #cosmos asked - are you using Spring Security?
If yes, the following should work:
In your SecurityConfig:
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
And in your Controller:
private String getPrincipal() {
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
Here is a method to used to get a user principal
#PutMapping(value = "/{userId}")
public ResponseEntity<?> updateUser(#Valid #RequestBody UserPutRequest updateRequest, #PathVariable String userId,
Authentication authentication) {
Optional<UserObject> userOptional = userRepository.findById(userId);
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
UserObject currentUser = userRepository.findByEmail(userPrincipal.getEmail());
}
if you are using keycloak you can get more details using this way
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
KeycloakPrincipal principal = (KeycloakPrincipal)auth.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
String username = accessToken.getPreferredUsername();
String emailID = accessToken.getEmail();
String lastname = accessToken.getFamilyName();
String firstname = accessToken.getGivenName();
String realmName = accessToken.getIssuer();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
Related
I have already done connect MongoDB using application.properties. But I want to connect MongoDB through mongoose.
This is my current configuration
This is DB Connection url setting in application.properties;
spring.data.mongodb.uri =mongodb+srv://hans123:Hans123#cluster0.avxi858.mongodb.net/?retryWrites=true&w=majority
spring.data.mongodb.database=test
spring.data.mongodb.port=27017
spring.data.mongodb.host=localhost
Model Class
#Document
#AllArgsConstructor
#NoArgsConstructor
#Data
public class User {
#Id
#Indexed
private String id;
#Indexed
private String address;
#Indexed
private String name;
#Indexed
private String email;
#Indexed
private String password;
#Indexed
private String role;
}
Repository Class
public interface userReporsitory extends MongoRepository<User,String> {
Optional<User> findByEmail(String email);
List<User> findAllByRole(String role);
}
Service Class
#AllArgsConstructor
#Service
public class userService {
private userReporsitory userReporsitory;
public User saveUser(User user){
return userReporsitory.save(user);
}
public User login(User user){
User response = userReporsitory.findByEmail(user.getEmail()).orElseThrow(()->new RuntimeException("User Not Found"));
if(!response.getPassword().equals(user.getPassword())){
throw new RuntimeException("Bad Credincials");
}
return response;
}
public List<User> findAllUsers(){
return userReporsitory.findAllByRole("user");
}
}
Controller Class
#CrossOrigin
#RestController
#AllArgsConstructor
#RequestMapping("api/v1/user")
public class userController {
private userService userService;
#PostMapping("/create")
public ResponseEntity<User> save(#RequestBody User user){
HttpStatus status = HttpStatus.EXPECTATION_FAILED;
User response = userService.saveUser(user);
if(response != null){
status = HttpStatus.CREATED;
}
return new ResponseEntity<>(response, status);
}
#PostMapping("/login")
public ResponseEntity<User> login(#RequestBody User user){
return new ResponseEntity<>(userService.login(user),HttpStatus.ACCEPTED);
}
#GetMapping("/userList")
public ResponseEntity<List<User>> userList(){
return new ResponseEntity<>(userService.findAllUsers(),HttpStatus.ACCEPTED);
}
}
In above answer you can encrypt the password as well (Additional Thing)
AuthRequest Class:
#Data
#AllArgsConstructor
public class AuthRequestDto {
private String userName;
private String password;
}
RegisterRequest Class:
#Data
#AllArgsConstructor
public class RegisterRequestDto {
private String userName;
private String password;
private String firstName;
private String email;
private String phone;
private String address;
}
UserService Class:
#AllArgsConstructor
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final JWTUtility jwtUtility;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByUsername(username).orElseThrow(()-> new UsernameNotFoundException("user name not found"));
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userEntity.getRole());
return new User(userEntity.getUsername() , userEntity.getPassword() , new ArrayList<>(Arrays.asList(authority)));
}
public UserEntity registerUser(RegisterRequestDto request){
//check user is already in
if(userRepository.existsByUsernameIgnoreCase(request.getUserName())){
throw new FieldValidationFaild("user name already exist in the system");
}
if(userRepository.existsByEmailIgnoreCase(request.getEmail())){
throw new FieldValidationFaild("email already exist in the system");
}
UserEntity saveToBe = UserEntity.builder()
.username(request.getUserName())
.password(new BCryptPasswordEncoder().encode(request.getPassword()))
.firstName(request.getFirstName())
.email(request.getEmail())
.phone((request.getPhone()))
.address(request.getAddress())
.role(Role.BUYER)
.build();
return userRepository.save(saveToBe);
}
public AuthRespondDto loginUser(AuthRequestDto authRequestDto){
final UserDetails userDetails = loadUserByUsername(authRequestDto.getUserName());
final String token = jwtUtility.generateToken(userDetails);
return new AuthRespondDto(token , authRequestDto.getUserName() , userDetails.getAuthorities().stream().findFirst().get().getAuthority());
}
public long getAuthUserId(String username){
UserEntity userEntity = userRepository.findByUsername(username).get();
return userEntity.getId();
}
public String getUserAddressByName(String username){
UserEntity userEntity = userRepository.findByUsername(username).get();
return userEntity.getAddress();
}
public String getUserPhoneByName(String username){
UserEntity userEntity = userRepository.findByUsername(username).get();
return userEntity.getPhone();
}
public String getUserEmailByName(String username){
UserEntity userEntity = userRepository.findByUsername(username).get();
return userEntity.getEmail();
}
public UserEntity getUserById(long id){
return userRepository.findById(id).orElseThrow(()-> new NotFoundException("user not found"));
}
}
AuthController Class (Controller Class):
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserService userService;
#PostMapping("/signin")
public ResponseEntity<AuthRespondDto> login(#RequestBody AuthRequestDto authRequestDto) throws BadCredentialsException {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authRequestDto.getUserName(),
authRequestDto.getPassword()
)
);
}catch (BadCredentialsException e){
//TODO add proper exception
throw new BadCredentialsException("INVALID_USERNAME_OR_PASSWORD" , e);
}
return new ResponseEntity<>(userService.loginUser(authRequestDto) , HttpStatus.ACCEPTED);
}
#PostMapping("/signup")
public ResponseEntity<UserEntity> register(#RequestBody RegisterRequestDto request){
// basic controller level validations
if(request.getUserName().isEmpty()){
throw new FieldValidationFaild("user name is required");
}
if(request.getPassword().isEmpty()){
throw new FieldValidationFaild("password is required");
}
if(request.getFirstName().isEmpty()){
throw new FieldValidationFaild("first name is required");
}
if(request.getEmail().isEmpty()){
throw new FieldValidationFaild("email is required");
}
if(request.getPhone().isEmpty()){
throw new FieldValidationFaild("phone is required");
}
if(request.getPhone().length() != 10){
throw new FieldValidationFaild("phone number length must be 10");
}
if(request.getAddress().isEmpty()){
throw new FieldValidationFaild("address is required");
}
return new ResponseEntity<>(userService.registerUser(request) , HttpStatus.CREATED);
}
}
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 the following entity class in my Spring boot project:
#Entity
#Table(name="user_account_entity")
#JsonDeserialize(using = UserAccountDeserializer.class)
public class UserAccountEntity implements UserDetails {
private final static String ROLE_USER = "ROLE_USER";
#Id
#NotBlank
private String id;
#NotBlank(message="Username cannot be empty")
#Email(message="Username must be a valid email address")
private String username;
#NotBlank(message="Password cannot be empty")
#Password
private String password;
#NotNull
#MapsId
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserEntity user;
public UserAccountEntity(final String username, final String password) {
this.password = password.trim();
this.username = username.trim();
}
public UserAccountEntity() {}
//setters and getters
}
and the following controller:
#RequestMapping("/api/authentication")
public class UserAccountControllerImpl implements UserAccountController {
#Autowired
private UserAccountService userAccountService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public String create(#RequestBody UserAccountEntity userAccount,
HttpServletResponse response) {
String username = userAccount.getUsername();
String password = userAccount.getPassword();
UserEntity user = new UserEntity();
UserAccountEntity userAccount = new UserAccountEntity(username,
passwordEncoder.encode(password));
userAccount.setUser(user);
userAccountRepository.save(userAccount);
return userAccountService.authenticateUserAndSetResponsenHeader(
username, password, response);
}
}
As you can see I have a custom validator for field password:
public class PasswordValidator implements ConstraintValidator<Password, String> {
public void initialize(Password constraintAnnotation) {}
public boolean isValid(String value, ConstraintValidatorContext context) {
String trimmedValue = value.trim();
if (trimmedValue.length() > 30 || trimmedValue.length() < 8) {
return false;
}
if (!Pattern.compile( "[0-9]" ).matcher(trimmedValue).find()) { // it doesn't contain any digit
return false;
}
if (trimmedValue.toUpperCase().equals(trimmedValue)) { //it's all upper-case
return false;
}
if (trimmedValue.toLowerCase().equals(trimmedValue)) { //it's all lower-case
return false;
}
return true;
}
}
How can I validate the password field in the request body in the controller but not when saving it? As I encode that field when saving it, the validation does not pass when saving it.
The first step is to use #Valid on the request body. So the new code change would look like this:
#Override
public String create (#Valid #RequestBody UserAccountEntity userAccount,
HttpServletResponse response) {
...
}
After this you can use InitBinder to bind your custom validator:
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidator(new PasswordValidator());
}
Note that the method marked with #InitBinder is part of your Controller class.
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.
I am successfully able to authenticate users, acquire a jsonwebtoken, and access protected urls. I am persisting a claim to the token with a role of "user" or "admin". What I would ideally, like to be able to do, after verifying the user, is persist the role based on the user role/roles, and protect specific url's in my api, based on those. I have the following setup. How do I apply security to a specific url to differentiate based on role in the token?
Package com.vicentex.api;
// imports excluded for brevity
#SpringBootApplication
public class VicentexTradingApiApplication {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/api/*");
return registrationBean;
}
public static void main(String[] args) {
SpringApplication.run(VicentexTradingApiApplication.class, args);
}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/api/users", method = RequestMethod.GET)
List<User> getAllUsers(){
return this.userService.getAllUsers();
}
#RequestMapping(value = "/api/users/{userName}",method = RequestMethod.GET)
User getUser(#PathVariable String userName) {
return this.userService.getUser(userName);
}
#RequestMapping(value = "/api/users", method = RequestMethod.POST)
void addUser(#RequestBody User user) {
this.userService.addUser(user);
}
#RequestMapping(value = "/api/users", method = RequestMethod.PUT)
void updateUser(#RequestBody User user) {
this.userService.updateUser(user);
}
#RequestMapping(value = "/api/users/{userName}", method = RequestMethod.DELETE)
void deleteUser(#PathVariable String userName) {
this.userService.deleteUser(userName);
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public User registerUser(#RequestBody User user) {
userService.addUser(user);
return user;
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#RequestBody User login) throws ServletException {
System.out.println("Controller Secret: " + Constants.secretKey);
String jwtToken = "";
if (login.getUserName() == null || login.getPassword() == null) {
throw new ServletException("Please fill in username and password");
}
String userName = login.getUserName();
String password = login.getPassword();
User user = userService.Authenticate(userName, password);
if (user == null) {
throw new ServletException("User not found.");
}
String pwd = user.getPassword();
if (!password.equals(pwd)) {
throw new ServletException("Invalid login. Please check your name and password.");
}
if (user.isAdmin()) {
jwtToken = Jwts.builder()
.setSubject(userName)
.claim("roles", "admin")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, Constants.secretKey)
.compact();
} else {
jwtToken = Jwts.builder()
.setSubject(userName)
.claim("roles", "user")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, Constants.secretKey)
.compact();
}
return jwtToken;
}
}
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
package com.vicentex.api.models;
#Document(collection = "Users")
public class User {
#Id
private ObjectId id;
private String userName;
private String fullName;
private String email;
private String password;
private String image;
private boolean isAdmin = false; //flagged for simplicity. Will be an array
Account acct;
#DBRef
List<Transaction> transactions;
public User() {
super();
}
public User(String userName, String fullName, String email, String password, String image) {
super();
this.userName = userName;
this.fullName = fullName;
this.email = email;
this.password = password; //note to remember to encrypt on creation
this.image = image;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
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;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean isAdmin) {
this.isAdmin = isAdmin;
}
public Account getAcct() {
return acct;
}
public void setAcct(Account acct) {
this.acct = acct;
}
public List<Transaction> getTransactions() {
return transactions;
}
public void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
}
In Spring you will use #PreAuthorize( "hasRole('somrole')" ) annotation on the method level. Spring is expecting the role to be in "authorities" claim.
In order to utilize #PreAuthorize, I had to include Spring Security as a dependency in my pom.xml. This forced me to have a password for the API, which isn't what I wanted. Worse, if I disabled the password, it basically defeated the purpose, as nothing else worked. I simply wanted to utilize the jsonwebtoken as a security mechanism over https, and protect a specific subset of url's based on roles in the token. So, what I actually ended up doing was this:
public class JwtFilter extends GenericFilterBean {
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
throws IOException, ServletException {
System.out.println("Secret Key: " + Constants.secretKey);
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String authHeader = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(req, res);
} else {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header");
}
final String token = authHeader.substring(7);
try {
final Claims claims = Jwts.parser().setSigningKey(Constants.secretKey).parseClaimsJws(token).getBody();
String[] roles = claims.get("roles").toString().split(",");
if (request.getRequestURI().contains("/api/")) {
if (!Arrays.asList(roles).contains("user")) {
System.out.println("ROLES: " + claims.get("roles"));
throw new ServletException("A minimum security credential of 'user' is required to access this resource!");
}
}
if (request.getRequestURI().contains("/api/admin")) {
if (!Arrays.asList(roles).contains("admin")) {
System.out.println("ROLES: " + claims.get("roles"));
throw new ServletException("This route is restricted to administrators only");
}
}
request.setAttribute("claims", claims);
} catch (final SignatureException e) {
throw new ServletException("Invalid token");
}
chain.doFilter(req, res);
}
}
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
// In addition, I changed the login code in the user controller
if (user.isAdmin()) {
jwtToken = Jwts.builder()
.setSubject(userName)
.claim("roles", "admin,user")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, Constants.secretKey)
.compact();
} else {
jwtToken = Jwts.builder()
.setSubject(userName)
.claim("roles", "user")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, Constants.secretKey)
.compact();
}
Technically, I could create a collection with the security mappings for role/uri, and loop through each one based on the URI being requested, without ever having to decorate a controller.