Java Spring with JWT auth,获取当前用户
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/50803727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring with JWT auth, get current user
提问by Alex
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
.
我有使用 JWT 令牌进行授权的 Spring Boot REST 应用程序。我想使用@AuthenticationPrincipal
注释在控制器中获取当前登录的用户。但是,null
如果我返回自定义模型loadUserByUsername
并且 auth 停止工作,它总是会返回。我的模型实现了UserDetails
.
I tried to extend the org.springframework.security.core.userdetails.User
but i get rid errors from JWTAuthenticationFilterthat default constructor not exists (ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
)
我试图扩展org.springframework.security.core.userdetails.User
但我摆脱了默认构造函数不存在的JWTAuthenticationFilter错误(ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
)
Whats wrong?
怎么了?
UserDetailsServiceImpl.java
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)
ApplicationUser.java(模型)
@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
JWT认证过滤器
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
JWT授权过滤器
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;
}
}
回答by MattIT
Check if you are using suitable annotation, because one of them is deprecated.
检查您是否使用了合适的注释,因为其中之一已被弃用。
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.
用于将 Authentication.getPrincipal() 解析为方法参数的注释。
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:
我不知道这是否是好的做法(我在 Spring 中还没有被认为是“专业人士”),但在我的个人项目中,我从控制器参数中传递的 HttpServletRequest 对象中获取令牌。然后我使用 JwtTokenUtil 类,它具有getUserFormToken(String token);
解析用户/用户名的方法。它看起来像这样:
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
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 thistutorial and implementation.
但我通常根据你的不同实现不同的过滤器。如果您有兴趣 - 我使用了本教程和实现。
回答by Alex
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.
然后在控制器中使用@AuthenticationPrincipal
注释检索。
public ApplicationUser getCurrentUser(@AuthenticationPrincipal ApplicationUser user) {
return user;
}
回答by José Eduardo Urzeda Ferreira
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:
在您的情况下,@AuthenticationPrincipal
将返回一个带有用户名的字符串,您可以通过在控制器中调用存储库并通过用户名获取用户或将存储库声明为 a@Bean
并执行以下操作来获取用户:
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;
}
}
回答by vaa
If this is still actual, I have just answered similar question here
如果这仍然是实际的,我刚刚在这里回答了类似的问题
Main point is to take authorization token
from header:
要点是authorization token
从标题中获取:
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.
之后你可以解码它并获得你需要的部分。