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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 23:49:09  来源:igfitidea点击:

Spring with JWT auth, get current user

javaspringspring-securityjwt

提问by Alex

I have Spring Boot REST application which uses JWT tokens for authorization. I want to get current logged user in controllers using @AuthenticationPrincipalannotation. But it always returns nullif i return custom model from loadUserByUsernameand 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.Userbut 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

UserDetailsS​​erviceImpl.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.

检查您是否使用了合适的注释,因为其中之一已被弃用。

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.

用于将 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 @AuthenticationPrincipalannotation.

然后在控制器中使用@AuthenticationPrincipal注释检索。

public ApplicationUser getCurrentUser(@AuthenticationPrincipal ApplicationUser user) {
    return user;
}

回答by José Eduardo Urzeda Ferreira

In your case the @AuthenticationPrincipalwill 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 @Beanand 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 tokenfrom 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.

之后你可以解码它并获得你需要的部分。