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);
}
}
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 am developing a microservices web application.
I have this microservices:
frontEnd service (all html files)
Account service (with Spring Security)
ZuulGateway service
Demo service (a simple rest controller that return a string)
EurekaServer service
My account service works. The login phase works and the JWT creation works. I save it in the header of the response:
response.addHeader("Authorization", "Bearer " + token);
This is the Ajax code in the frontEnd:
/* sign in submit function */
$("#submit").click(function(e) {
e.preventDefault();
$.ajax({ /* Ajax call to AccountMicroservice for login */
url : 'http://localhost:8762/token/generate-token',
type : "POST",
data : {
username : $("#username").val(),
password : $("#password").val()
},
success : function(data) {
console.log(data.token);
window.location.href = 'http://localhost:8762/test';
},
error : function(result) {
alert("Sign in failed!");
console.log(result);
}
});
});
The problems arose with the addition of Spring Cloud Netflix Zuul. The gateway server port is 8762.
Each request to 'http://localhost:8762/token/generate-token' return a 401 error.
These are the Zuul gateway service. I omitted the import.
application.properties:
server.port=8762
spring.application.name=gateway-service
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER_URL:http://localhost:8761/eureka}
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
zuul.routes.test-service.path=/test/**
zuul.routes.test-service.service-id=test-service
zuul.routes.account-service.path=/token/**
zuul.routes.account-service.service-id=account-service
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie
webSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterAfter(new JwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
// allow all who are accessing "auth" service
.antMatchers(HttpMethod.POST, "/token/**", "/signup").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated();
}
//The CORS filter bean - Configures allowed CORS any (source) to any
//(api route and method) endpoint
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(CorsConfiguration.ALL);
//config.addAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return source;
}
}
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(Authentication authentication) {
final String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {
final JwtParser jwtParser = Jwts.parser().setSigningKey(SIGNING_KEY);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
}
JwtTokenAuthenticationFilter:
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
// 2. validate the header and check the prefix
if (header != null && header.startsWith(TOKEN_PREFIX)) {
// 3. Get the token
authToken = header.replace(TOKEN_PREFIX,"");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
System.out.println("\n\n\n\n\n\n\n\n\n\n" + username + "\n\n\n\n\n\n\n\n\n\n");
} catch (IllegalArgumentException e) {
logger.error("c'รจ stato un errore durante il reperimento dello username dal token", e);
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 4. Validate the token
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
//UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
JwtAuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
String json = String.format("{\"message\": \"%s\"}", authException.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
}
constants:
public class Constants {
public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
public static final String SIGNING_KEY = "devglan123r";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String AUTHORITIES_KEY = "scopes";
}
i want to get UserDetails from HttpServletRequest when I have SessionAuthenticationException - mean that session already exist for current use, but get null
My Hadnler is
public class SecurityErrorHandler extends SimpleUrlAuthenticationFailureHandler {
private static final String FORCE_PARAMETER_NAME = "force";
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
//if session already exist
if (exception.getClass().isAssignableFrom(SessionAuthenticationException.class)) {
logger.debug("Session already exist");
Principal userPrincipal = request.getUserPrincipal();
}
}
}
Can anyone help me?
There is no easy way. You need to get it from Authorization header
String authHeader = request.getHeader("Authorization");
byte[] base64Token =
header.trim().substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded = java.util.Base64.getDecoder().decode(base64Token);
String token = new String(decoded, StandardCharsets.UTF_8);
int delim = token.indexOf(":");
String userName = token.substring(0, delim);
The above code can look hacky but it is actually what spring security BasicAuthenticationConverter does. https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java#L94
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.
To implement HMAC authentication I made my own filter, provider and token.
RestSecurityFilter:
public class RestSecurityFilter extends AbstractAuthenticationProcessingFilter {
private final Logger LOG = LoggerFactory.getLogger(RestSecurityFilter.class);
private AuthenticationManager authenticationManager;
public RestSecurityFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
public RestSecurityFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AuthenticationRequestWrapper request = new AuthenticationRequestWrapper(req);
// Get authorization headers
String signature = request.getHeader("Signature");
String principal = request.getHeader("API-Key");
String timestamp = request.getHeader("timestamp");
if ((signature == null) || (principal == null) || (timestamp == null))
unsuccessfulAuthentication(request, response, new BadHMACAuthRequestException("Authentication attempt failed! Request missing mandatory headers."));
// a rest credential is composed by request data to sign and the signature
RestCredentials credentials = new RestCredentials(HMACUtils.calculateContentToSign(request), signature);
// Create an authentication token
return new RestToken(principal, credentials, Long.parseLong(timestamp));
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
LOG.debug("Filter request: " + req.toString());
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
chain.doFilter(request, response);
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null)
unsuccessfulAuthentication(request, response, new BadHMACAuthRequestException("Authentication attempt failed !"));
} catch (InternalAuthenticationServiceException failed) {
LOG.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
} catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
}
}
}
Authentication provider:
#Component
public class RestAuthenticationProvider implements AuthenticationProvider {
private final Logger LOG = LoggerFactory.getLogger(RestAuthenticationProvider.class);
private ApiKeysService apiKeysService;
#Autowired
public void setApiKeysService(ApiKeysService apiKeysService) {
this.apiKeysService = apiKeysService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RestToken restToken = (RestToken) authentication;
// api key (aka username)
String principal = restToken.getPrincipal();
LOG.info("Authenticating api key: '" + principal + "'");
// check request time, 60000 is one minute
long interval = Clock.systemUTC().millis() - restToken.getTimestamp();
if ((interval < 0) && (interval > 60000))
throw new BadHMACAuthRequestException("Auth Failed: old request.");
// hashed blob
RestCredentials credentials = restToken.getCredentials();
// get secret access key from api key
ApiKey apiKey = apiKeysService.getKeyByName(principal).orElseThrow(() -> new NotFoundException("Key not found for: '" + principal + "'"));
String secret = apiKey.getApiKey();
// calculate the hmac of content with secret key
String hmac = HMACUtils.calculateHMAC(secret, credentials.getRequestData());
LOG.debug("Api Key '{}', calculated hmac '{}'");
// check if signatures match
if (!credentials.getSignature().equals(hmac)) {
throw new BadHMACAuthRequestException("Auth Failed: invalid HMAC signature.");
}
return new RestToken(principal, credentials, restToken.getTimestamp(), apiKeysService.getPermissions(apiKey));
}
#Override
public boolean supports(Class<?> authentication) {
return RestToken.class.equals(authentication);
}
}
I don't know how to configure WebSecurityConfig to authenticate every request with my filter and Authentication Provider. I assume I need to create #Bean to initialize RestSecurityFilter. Also JavaDoc for AbstractAuthenticationProcessingFilter says I need to the authenticationManager property. I would appreciate working solution with custom filter, provider and token.
I'm not familiar with Spring Boot, but I saw your comment on my question How To Inject AuthenticationManager using Java Configuration in a Custom Filter
In a traditional Spring Security XML configuration, you would specify your custom RestSecurityFilter like so
<http use-expressions="true" create-session="stateless" authentication-manager-ref="authenticationManager" entry-point-ref="restAuthenticationEntryPoint">
<custom-filter ref="restSecurityFilter" position="FORM_LOGIN_FILTER" />
</http>
More information http://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#ns-custom-filters