I have made a user login interface where the user gets authenticated using spring security.
I have made an AuthenticationSuccessHandler which redirects the user to a new page.
I also want to implement a loginController in order to get the name of user logged in as well as displaying error messages for wrong credentials. Here is my Handler code :
public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
protected MySimpleUrlAuthenticationSuccessHandler() {
super();
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
protected void handle(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
final String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(final Authentication authentication) {
boolean isUser = false;
boolean isAdmin = false;
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_USER")) {
isUser = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
if (isUser) {
return "/static_htm.html";
} else if (isAdmin) {
return "/console.html";
} else {
throw new IllegalStateException();
}
}
And my controller code :
#Controller
public class HelloController {
#RequestMapping(value="/login", method = RequestMethod.GET)
public String printWelcome(ModelMap model, Principal principal ) {
String name = principal.getName();
model.addAttribute("username", name);
model.addAttribute("message", "Spring Security Hello World");
return "static_htm"; //page after successful login
}
#RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model) {
return "login"; //login page
}
#RequestMapping(value="/loginfailed", method = RequestMethod.GET)
public String loginerror(ModelMap model) {
//String errormessage = resources.getMessage("login.error", null, null);
model.addAttribute("error", "true");
return "login"; //login page
}
}
The handler works fine but I am not able to get the user name as well as the error message. What should I do to make both the handler and controller work together ?
Any suggestions are greatly appreciated.
From your question I presume you want to get the user name in the login controller.
If not so, feel free to disregard my answer.
You may have gotten it backward actually.
Success handler is somewhat like a custom implementation of "default-target-url".
So it is actually executed after login controller...
When login is successful, and there's no previously requested path (this is implemented by SavedRequestAwareAuthenticationSuccessHandler) then the request will be sent to the "default-target-url".
Or when there's a custom success handler, the success handler will determine the path it goes to.
Related
I've created backend for my mobile application with REST API and JWT authentication/authorization.
Then I created android application using Retrofit.
After retrieving JWToken from /login endpoint I've created GET request on a server-side to parse username of currently logged user with token, and then I call method on client-side.
UserController.java (server-side)
#RestController
#RequestMapping(path = "/user")
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping
public String getCurrentUser(#AuthenticationPrincipal Object user) {
user = SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
return user.toString();
}
}
But I'm not sure if it's a right way to develop it like this.
Let's say I have two tables in my database.
One with login credentials that are being used in Authentication
Second with users personal data
and now I want to display First name and last name of a user.
Now, the only information after login I have is users username that he logged with and if I want to get more information I have to somehow make Queries on client side to:
first - get id of a user where username = username that I got from token
then - get object of users_data where user_id = id from the first query
and I don't think this process should be done on the client side(correct me if I'm wrong, please).
Question
So my question is what should I do fulfill this scenario where I want to get all information about user where I have only his username in client-side app. Should I make changes in my backend, or stick to making queries from mobile app?
(Server-side)
AuthenticationFilter.java
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response
) throws AuthenticationException {
// Mapping credentials to loginviewmodel
LoginViewModel credentials = null;
try {
credentials = new ObjectMapper().readValue(request.getInputStream(), LoginViewModel.class);
} catch (IOException e) {
e.printStackTrace();
}
// Creating login token
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword(),
new ArrayList<>()
);
// Authenticate user
Authentication auth = authenticationManager.authenticate(authenticationToken);
return auth;
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult
) throws IOException, ServletException {
// Grab current user
UserImpl principal = (UserImpl) authResult.getPrincipal();
// Create JWT Token
String token = JWT.create()
.withSubject(principal.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.sign(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()));
// Add token in response(this is syntax of token)
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + token);
}
}
AuthorizationFilter.java
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
public JwtAuthorizationFilter(
AuthenticationManager authenticationManager,
UserRepository userRepository
) {
super(authenticationManager);
this.userRepository = userRepository;
}
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
// Read authorization header with JWT Token
String header = request.getHeader(JwtProperties.HEADER_STRING);
if (header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
}
// Try get user data from DB to authorize
Authentication authentication = getUsernamePasswordAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(HttpServletRequest request) {
String token = request.getHeader(JwtProperties.HEADER_STRING);
if (token != null) {
// parse and validate token
String username = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET.getBytes()))
.build()
.verify(token.replace(JwtProperties.TOKEN_PREFIX, ""))
.getSubject();
if (username != null) {
User user = userRepository.findByUsername(username);
UserImpl principal = new UserImpl(user);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, principal.getAuthorities());
return auth;
}
return null;
}
return null;
}
}
UserImpl.java
public class UserImpl implements UserDetails {
private User user;
public UserImpl(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
// Get list of roles (ROLE_name)
this.user.getRoleList().forEach( role -> {
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + role);
authorities.add(authority);
});
return authorities;
}
#Override
public String getPassword() {
return this.user.getPassword();
}
#Override
public String getUsername() {
return this.user.getUsername();
}
}
(Client-side)
Method for parsing username from currently logged in User:
public void getCurrentUser() {
Call<String> call = ApiClient.getUserService(getApplicationContext()).getCurrentUser();
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
String user = response.body();
nameOfUserView.setText(user);
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
nameOfUserView.setText(t.getMessage());
}
});
}
There is a loophole in your logic. Lets see
#1 - It is fine. Based on JWT Token, you are fetching username
#2 - Using token in header, you are fetching other details by sending username.
In #2, what if I send username of any other user instead of logged in user. System will still respond with the details. So, any logged in user will be able to see details of any user.
To handle this, you should use some DTO class which has all required fields say UserReturnData. Structure will be
public class UserReturnData
{
String username;
List<String> roles;
Long id;
//more fields as per requirement
}
Then in your current user call, populate this data based on authorisation header. Do not send any username. Authorisation header should be sufficient to fetch user details. Sample:
public UserReturnData fetchUserDetails()
{
UserReturnData userReturnData = new UserReturnData();
List<String> roles = new ArrayList<String>();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
userReturnData.setUsername(auth.getName());
Long id = uRepo.findId(auth.getName());
userReturnData.setId(id);
Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>) SecurityContextHolder
.getContext().getAuthentication().getAuthorities();
for (SimpleGrantedAuthority authority : authorities)
{
roles.add(authority.getAuthority());
}
userReturnData.setRoles(roles);
//Populate other required fields here.
return userReturnData;
}
Whenever you need details of logged-in user. You can make call to current user API with only Authorization token and get logged in user information
I'm working with RedisHttpSession and my basic goal is to save the staff object in the session object on successful login, retrieve it wherever I need and destroy the session on logout.
On successful login, this is what I'm doing:
Staff staff = staffService.getEmailInstance(body.getEmailId());
request.getSession(true).setAttribute("staff", staff);
And Logout is simply this:
request.getSession().invalidate();
In a different controller, I am calling this utility method that checks if the staff is logged in: util.isStaffLoggedIn(request, response, StaffRole.EDITOR); If the staff is logged in, the API proceeds, else the user is redirected to the login page.
#Service
public class Util {
public boolean isStaffLoggedIn(HttpServletRequest request, HttpServletResponse response, StaffRole staffRole)
throws PaperTrueInvalidCredentialsException, PaperTrueJavaException {
Staff staff = (Staff) request.getSession().getAttribute("staff");
if (!isObjectNull(staff) && staff.getStaffRole().equals(staffRole)) {
return true;
}
invalidateSessionAndRedirect(request, response);
return false;
}
public void invalidateSessionAndRedirect(HttpServletRequest request, HttpServletResponse response)
throws PaperTrueJavaException, PaperTrueInvalidCredentialsException {
request.getSession().invalidate();
try {
response.sendRedirect(ProjectConfigurations.configMap.get("staff_logout_path"));
} catch (IOException e) {
throw new PaperTrueJavaException(e.getMessage());
}
throw new PaperTrueInvalidCredentialsException("Staff not loggedIn");
}
}
Now while the app is running, the get-jobs API is called immidiately after successful login. Most of the times the request.getSession().getAttribute("staff") method works fine and returns the 'staff' object but, once in a while, it returns null. This doesn't happen often, but it does. I printed the session Id to see if they are different after logout, and they were. After each logout I had a new session Id. I even checked if the staff object I retrieved from the database was null, but it wasn't.
The staff object was successfully saved in the sessions but I wasn't able to retrieve it in othe APIs. This is how my session config looks:
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10800)
public class SessionConfig {
HashMap<String, String> configMap = ProjectConfigurations.configMap;
#Bean
public LettuceConnectionFactory connectionFactory() {
int redisPort = Integer.parseInt(configMap.get("redis_port"));
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(
configMap.get("redis_host"), redisPort);
redisStandaloneConfiguration.setPassword(configMap.get("redis_password"));
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("PTSESSIONID");
serializer.setSameSite("none");
serializer.setUseSecureCookie(!configMap.get("staff_logout_path").contains("localhost"));
return serializer;
}
}
Please let me know if I missed out anything. Thanks in advance.
Update 1
I'm not invalidating the session anymore and I've replaced request.getSession(true).setAttribute("staff", staff); to request.getSession().setAttribute("staff", staff);
I'm setting the 'staff' in StaffController and getting it in EditorController. Here's how I'm setting it:
#RestController
#RequestMapping(path = { "/staff" }, produces = "application/json")
public class StaffApiController {
private final HttpServletRequest request;
private final HttpSession httpSession;
#Autowired
private StaffService staffService;
#Autowired
StaffApiController(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
this.request = request;
this.httpSession = session;
}
#PostMapping("/login")
public ResponseEntity<StaffLoginResponse> login(#Valid #RequestBody StaffLoginBody body) {
StaffLoginResponse staffLoginResponse = new StaffLoginResponse();
try {
if (!staffService.isValidLogin(body.getEmailId(), body.getPassword())) {
throw new PaperTrueInvalidCredentialsException("Invalid Credentials");
}
Staff staff = staffService.getEmailInstance(body.getEmailId());
httpSession.setAttribute("staff", staff);
staffLoginResponse.setEmail(staff.getEmail()).setRole(staff.getStaffRole().getValue())
.setStaffID(staff.getId()).setStatus(new Status("Staff Login Successful"));
} catch (PaperTrueException e) {
httpSession.removeAttribute("staff");
staffLoginResponse.setStatus(new Status(e.getCode(), e.getMessage()));
}
return ResponseEntity.ok(staffLoginResponse);
}
#PostMapping("/logout")
public ResponseEntity<Status> logout() {
httpSession.removeAttribute("staff");
return ResponseEntity.ok(new Status("Staff Logged Out Successfully"));
}
}
If you are using Spring Security, you can create a custom "/login" endpoint that authenticates the user by setting the SecurityContext.
You can use the default logout behaviour provided by Spring Security.
If you do not need to supply the credentials in the body, you can use the default login behaviour provided by Spring Security and omit this Controller altogether.
This is intended as a starting point.
It does not offer comprehensive security, for example it may be vulnerable session fixation attacks.
#RestController
public class LoginController {
private AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#PostMapping("/login")
public void login(#RequestBody StaffLoginBody body, HttpServletRequest request) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(body.getUsername(), body.getPassword());
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession();
session.setAttribute("staff", "staff_value");
}
#GetMapping("/jobs")
public String getStaffJobs(HttpServletRequest request) {
return request.getSession().getAttribute("staff").toString();
}
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// expose AuthenticationManager bean to be used in Controller
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
)
// use built in logout
.logout(logout -> logout
.deleteCookies("PTSESSIONID")
);
}
}
You will need to add the Spring Security dependency to use this code org.springframework.boot:spring-boot-starter-security.
Problem:
I would like to get/extract the username/email only from authenticate.getName()... if possible, not by using parsing the string.
authentication.getName() or principal.getName() values:
[username]: org.springframework.security.core.userdetails.User#21463e7a: Username: butitoy#iyotbihagay.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities
In this example, I would like to get only the value of Username which is butitoy#iyotbihagay.com
Solution:
Since I only want to get the username/email (butitoy#iyotbihagay.com), and it is returning the whole principal content/text (above), I replaced the value I set in the subject from the pricipal value... to the email value.. and it works now.
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String email = auth.getName();
String principal = auth.getPrincipal().toString();
Date expiration = new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME);
String token = Jwts.builder()
.setSubject(email) //from principal to email
.setExpiration(expiration)
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes())
.compact();
AuthenticatedUser loginUser = new AuthenticatedUser(email);
loginUser.setToken(token);
String jsonUser = Util.objectToJsonResponseAsString(loginUser, "user");
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
res.setContentType("application/json");
res.setCharacterEncoding(ConstantUtil.DEFAULT_ENCODING);
res.getWriter().write(jsonUser);
}
I can now get the username/email value using different ways like the one you guys are suggesting... even the one I am currently using. I do not need any special parsing now just to get the email value from the Authentication object.
On my previous non RESTful application using Spring... I can easily get the username using Authentication class injected in the controller method parameter.
Controller:
...
public Ticket getBySwertresNo(Authentication authentication, #PathVariable String swertresNo) {
logger.debug("Inside getBySwertresNo: " + swertresNo);
System.out.println("\n[username]: " + authentication.getName() + "\n");
return m_sugalService.getSwertresInfoBySwertresNo(swertresNo);
}
...
Console:
[username]: butitoy#iyotbihagay.com
Now, on my current project... I used a RESTful approach and after successful authentication, I am returning a token which will be used/injected in the request header. I can login using the token... but when I get the value of authentication.getName()... the return is not just the email address but it contains some other information.
Console (REST + JWT):
[username]: org.springframework.security.core.userdetails.User#21463e7a: Username: butitoy#iyotbihagay.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Not granted any authorities
I would like to get only the username value which is "butitoy#iyotbihagay.com".
JWT Authentication Filter:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
String username = req.getParameter("username");
String password = req.getParameter("password");
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(authenticationToken);
return authentication;
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String email = auth.getName();
String principal = auth.getPrincipal().toString();
Date expiration = new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME);
String token = Jwts.builder()
.setSubject(principal)
.setExpiration(expiration)
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes())
.compact();
AuthenticatedUser loginUser = new AuthenticatedUser(email);
loginUser.setToken(token);
String jsonUser = Util.objectToJsonResponseAsString(loginUser, "user");
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
res.setContentType("application/json");
res.setCharacterEncoding(ConstantUtil.DEFAULT_ENCODING);
res.getWriter().write(jsonUser);
}
}
JWT Authorization Filter:
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(SecurityConstants.HEADER_STRING);
if (header == null || !header.startsWith(SecurityConstants.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(SecurityConstants.HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SecurityConstants.SECRET.getBytes())
.parseClaimsJws(token.replace(SecurityConstants.TOKEN_PREFIX, ""))
.getBody()
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
I think you can use authentication.getName and principal.getName in the injected controller argument of type Authentication and Principal:
#Controller
#RequestMapping("/info")
public class GetNameController {
#RequestMapping(value = "/name", method = RequestMethod.GET)
public String getName(Authentication authentication, Principal principal) {
System.out.println(authentication.getName());
System.out.println("-----------------");
System.out.println(principal.getName());
return "";
}
}
could produce
admin
-----------------
admin
It doesn't matter whether you are using token or basic spring security authentication as far as Authentication/Principal object is concerned.
In case of spring security, you can get your current logged in user by
1. Object user = Authentication authentication (as you are already doing)
2.
Object user = SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
In both cases, user will contains the user object you returning from UserDetailsService.loadUserByUsername(...). So using default UserDetailsService you will get spring security's User object which contains basic user information like username, password etc.
So in case if you are using default spring's UserDetailsService, then you can get your current logged in user simply by
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();
You can use
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("--------------------------------------------------------------");
JwtUser jwtUser = (JwtUser) auth.getPrincipal();
//Get the username of the logged in user: getPrincipal()
System.out.println("auth.getPrincipal()=>"+jwtUser.getUsername() );
//Get the password of the authenticated user: getCredentials()
System.out.println("auth.getCredentials()=>"+auth.getCredentials());
//Get the assigned roles of the authenticated user: getAuthorities()
System.out.println("auth.getAuthorities()=>"+auth.getAuthorities());
//Get further details of the authenticated user: getDetails()
System.out.println("auth.getDetails()=>"+auth.getDetails());
System.out.println("--------------------------------------------------------------");
Have not seen so far any accepted answer, maybe this will help:
use JwtTokenUtils.debugPrint(); call from below class. For other token payload see what is available inside tokenMap.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.*;
import static org.springframework.security.oauth2.provider.token.AccessTokenConverter.EXP;
public class JwtTokenUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtils.class);
private static Format dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static ObjectMapper objectMapper = new ObjectMapper();
public static void debugPrint() {
try {
Map<String, Object> tokenMap = decode(getToken());
logger.debug("JwtTokenUtils:debugPrint jwt:"
+ " user_name {" + tokenMap.get("user_name")
+ "}, expired {" + convertTime((long)tokenMap.get(EXP))
+ "}");
} catch (Exception e) {
logger.error("JwtTokenUtils:debugPrint exception: " + e);
}
}
private static String getToken() {
return getAuthorizationHeader().split(" ")[1];
}
private static String getAuthorizationHeader() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return request.getHeader("Authorization");
}
private static Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decode(token);
String claimsStr = jwt.getClaims();
TypeReference<HashMap<String,Object>> typeRef = new TypeReference<>() {};
return objectMapper.readValue(claimsStr, typeRef);
}
catch (Exception e) {
throw new InvalidTokenException("Cannot convert access token to JSON", e);
}
}
private static String convertTime(long time){
Date date = new Date(time * 1000);
return dateFormat.format(date);
}
}
I need redirecting to different pages after logging depending on Role of user. I use class CustomSuccess Handler for this purpose:
#Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
protected void handle(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String url = determineTargetUrl(authentication);
if(response.isCommitted()){
return;
}
redirectStrategy.sendRedirect(request, response, url);
}
protected String determineTargetUrl(Authentication authentication){
Collection<? extends GrantedAuthority> authorities =
authentication.getAuthorities();
List<String> roles = new ArrayList<>();
for(GrantedAuthority authority: authorities){
roles.add(authority.getAuthority());
}
if(roles.contains("ROLE_ADMIN")){
return "/admin/adminPage";
}else if(roles.contains("ROLE_USER")){
return "/personal/profile";
}else return "/login";
}
}
And I need SavedRequestAwareAuthenticationSuccessHandler for redirecting to targetUrl, if one exists.
#Bean
public SavedRequestAwareAuthenticationSuccessHandler
savedRequestAwareAuthenticationSuccessHandler() {
SavedRequestAwareAuthenticationSuccessHandler auth
= new SavedRequestAwareAuthenticationSuccessHandler();
auth.setTargetUrlParameter("targetUrl");
return auth;
}
Method http.successHandler(...) overrides previous value of successHandler.
How can I use both this classes in my application? Help please)
We added property isBlocked for users.
When following property is set user cannot login on our site.
We want to render error message "your account is temporary blocked..."
But I have not ideas how to pass this message to loginFailed controller method.
I have following spring-security configuration:
public class XXXSecurityServiceImpl implements UserDetailsService {
#Autowired
private TerminalAdminDao terminalAdminDao;
#Override
public UserDetails loadUserByUsername(String adminName) throws UsernameNotFoundException,
DataAccessException {
TerminalAdmin admin = terminalAdminDao.findAdminByEmail(adminName);
UserDetails userDetails = null;
if (admin != null) {
Set<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();
for (AdminRole adminRole : admin.getAdminRoles()) {
authorities.add(new SimpleGrantedAuthority(adminRole.getRole()));
}
userDetails = new User(admin.getEmail(), admin.getPassword(), true, true, true, !admin.isBlocked(),
authorities);
}
return userDetails;
}
admin failed controlled method:
#RequestMapping(value = "/loginAdminFailed", method = RequestMethod.GET)
public String loginError(HttpSession session) {
session.setAttribute("login_error", "true");
return "admin/login";
}
How to understand in controller that user is blocked?
Basically you need to register custom authentication provider and custom authentication failure handler to handle authentication exceptions.
Check this SO question.
This works
added to controller:
#RequestMapping(value = "/loginAdminFailed", method = RequestMethod.GET)
public String loginError(HttpSession session, HttpServletRequest request) {
session.setAttribute("message", getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION"));
return "admin/login";
}
// customize the error message
private String getErrorMessage(HttpServletRequest request, String key) {
Exception exception = (Exception) request.getSession().getAttribute(key);
String error = "";
if (exception instanceof BadCredentialsException) {
error = messageSource.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", null, LOCALE_RU);
} else if (exception instanceof LockedException) {
error = messageSource.getMessage(
"AbstractUserDetailsAuthenticationProvider.accountIsLocked", null, LOCALE_RU);
} else {
error = messageSource.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", null, LOCALE_RU);
}
return error;
}
But this looks ugly