【问题标题】:Spring with JWT auth, get current userSpring with JWT auth,获取当前用户
【发布时间】:2018-11-21 01:23:36
【问题描述】:

我有使用 JWT 令牌进行授权的 Spring Boot REST 应用程序。我想使用 @AuthenticationPrincipal 注释在控制器中获取当前登录的用户。但如果我从loadUserByUsername 返回自定义模型并且授权停止工作,它总是返回null。我的模型实现了UserDetails

我尝试扩展 org.springframework.security.core.userdetails.User,但我从 JWTAuthenticationFilter 中删除了默认构造函数不存在的错误 (ApplicationUser creds = new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);)

怎么了?

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(模型)

@Entity
@Table(name = "users")
public class ApplicationUser implements UserDetails {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(nullable = false)
    private String password;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
}

JWTAuthenticationFilter

public class JWTAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(LOGIN_URL));

        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            ApplicationUser creds = new ObjectMapper()
                    .readValue(req.getInputStream(), ApplicationUser.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(((ApplicationUser) auth.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                .compact();

        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    }
}

JWTAuthorizationFilter

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user;
            try {
                user = Jwts.parser()
                        .setSigningKey(SECRET.getBytes())
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
            } catch (SignatureException e) {
                return null;
            }

            if (user != null) return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());

            return null;
        }
        return null;
    }
}

【问题讨论】:

    标签: java spring spring-security jwt


    【解决方案1】:

    在您的情况下,@AuthenticationPrincipal 将返回一个带有用户名的字符串, 您可以通过调用控制器中的存储库并通过用户名获取用户或将存储库声明为@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;
    }
    
    }
    

    【讨论】:

      【解决方案2】:

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

      Documentation - deprecated!

      Documentation - fine!


      此外,请注意将用户名(字符串)解析为参数,而不是用户类型:

      用于将 Authentication.getPrincipal() 解析为方法参数的注解。

      Check this topic as well!可以帮上忙。


      我不知道这是否是好的做法(我在 Spring 中还没有被认为是“专业人士”),但在我的个人项目中,我从控制器参数中传递的 HttpServletRequest 对象获取令牌。然后我使用 JwtTokenUtil 类,它有getUserFormToken(String token); 方法来解析用户/用户名。它看起来像这样:

      控制器

      @Autowired
      TestService testService;
      
      @Autowired
      UserService userService;
      
      @Autowired
      private JwtTokenUtil jwtTokenUtil;
      
      @RequestMapping(value="/test", method = RequestMethod.GET, produces = "application/json")
      @ResponseBody
      public List<Test> getTestsListByUserId(HttpServletRequest req){
          String token = req.getHeader(HEADER_STRING).replace(TOKEN_PREFIX,"");
          return testService.findByUserId(userService.findByUsername(jwtTokenUtil.getUsernameFromToken(token)));
      }
      

      JwtTokenUtil

      @Component
      public class JwtTokenUtil implements Serializable {
      
      public String getUsernameFromToken(String token) {
          return getClaimFromToken(token, Claims::getSubject);
      }
      
      public Date getExpirationDateFromToken(String token) {
          return getClaimFromToken(token, Claims::getExpiration);
      }
      
      public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
          final Claims claims = getAllClaimsFromToken(token);
          return claimsResolver.apply(claims);
      }
      
      private Claims getAllClaimsFromToken(String token) {
          return Jwts.parser()
                  .setSigningKey(SIGNING_KEY)
                  .parseClaimsJws(token)
                  .getBody();
      }
      
      private Boolean isTokenExpired(String token) {
          final Date expiration = getExpirationDateFromToken(token);
          return expiration.before(new Date());
      }
      
      public String generateToken(User user) {
          return doGenerateToken(user.getUsername());
      }
      
      private String doGenerateToken(String subject) {
      
          Claims claims = Jwts.claims().setSubject(subject);
          claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
      
          return Jwts.builder()
                  .setClaims(claims)
                  .setIssuer("issuer")
                  .setIssuedAt(new Date(System.currentTimeMillis()))
                  .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
                  .signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
                  .compact();
      }
      
      public Boolean validateToken(String token, UserDetails userDetails) {
          final String username = getUsernameFromToken(token);
          return (
                 username.equals(userDetails.getUsername())
                         && !isTokenExpired(token));
          }
      
      }
      

      但我通常会根据你的不同过滤器实现。如果您有兴趣 - 我使用了this 教程和实现。

      【讨论】:

        【解决方案3】:

        要检索自定义模型,我会执行以下操作:

        从数据库中获取模型并将其设置为主体。

         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;
            }
        

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

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

        【讨论】:

          【解决方案4】:

          如果这仍然是实际的,我刚刚回答了类似的问题here

          重点是从标题中取出authorization token

              HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
              String token = request.getHeader("Authorization").split(" ")[1];
          

          之后,您可以对其进行解码并获得您需要的部分。

          【讨论】:

            猜你喜欢
            • 2019-02-02
            • 2018-12-16
            • 2020-08-05
            • 1970-01-01
            • 2014-06-10
            • 2017-08-12
            • 2017-02-13
            • 2016-07-18
            • 2017-12-20
            相关资源
            最近更新 更多