如何在spring-boot中的JWT令牌中实现用户信息?

时间:2018-05-19 11:20:59

标签: spring spring-boot

大家好!

我正在开发一个带有弹簧启动(JWT和Spring安全)和前端Angular5的项目。

我现在处于需要使用JWT令牌发送经过身份验证的用户ID的阶段,因此我可以在许多操作中使用它前端...

第一个问题

我没有使用Auth2,只有JWT,我该如何实现?我找不到JWT的任何complet例子。

第二个问题

我看到一些例子,他们使用实现TokenEnhancer的类UserTokenEnhancer,它在扩展AuthorizationServerConfigurerAdapter的类中使用,而我的config类正在扩展WebSecurityConfigurerAdapter。

对于如何使用JWT,有没有类似的方法?

随着时间的推移搜索这些信息但没有结果,希望我在这里得到任何答案。

提前谢谢:)。

修改

这是 JWTAuthenticationFilter.java

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

             private AuthenticationManager authenticationManager;

            @Autowired
            private UserRepo userRepo;

            @Autowired
             private AccountService accountService;

             @Autowired
              public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
              this.authenticationManager = authenticationManager;
           }

            @Override
            @Autowired
            public void setAuthenticationManager(AuthenticationManager authenticationManager) {
                super.setAuthenticationManager(authenticationManager);
            }

        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

            AppUser appUser = null;
            try {
                appUser=new ObjectMapper().readValue(request.getInputStream(),AppUser.class);
            } catch (IOException e) {
               throw new RuntimeException(e.getMessage());
            }

             return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(appUser.getUsername(),appUser.getPassword()));
        }

        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

            User springUser = (User)authResult.getPrincipal();
            String jwt = Jwts.builder()
            .setSubject(springUser.getUsername())
                    .setExpiration(new Date(System.currentTimeMillis()+SecurityConstants.EXPIRATION_TIME))
                    .signWith(SignatureAlgorithm.HS256,SecurityConstants.SECRET)
                    .claim("roles",springUser.getAuthorities())
                    .compact();

            System.out.println("****"+jwt);

              AppUser app = userRepo.findByUsername(springUser.getUsername());
              Long id = app.getId();

              ObjectMapper objectMapper = new ObjectMapper();
              JsonNode jsonJwt = objectMapper.readTree(jwt);
              ((ObjectNode)jsonJwt).put("userId", id);



               response.addHeader(SecurityConstants.HEADER_STRING,SecurityConstants.TOKEN_PREFIX+objectMapper.writeValueAsString(jsonJwt));

        }
    }

修改

Erros我正在关注JsonParseException行 JsonNode jsonJwt = objectMapper.readTree(jwt);

这是错误:

 com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'eyJhbGciOiJIUzI1NiJ9': was expecting ('true', 'false' or 'null')
 at [Source: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTUyNzU5NjA2Mywicm9sZXMiOlt7ImF1dGhvcml0eSI6IkFETUlOIn1dfQ.gkoGLsdVxgJURhfGQkn5pbs6KyO6ikCAILQeR4j0ikk; line: 1, column: 21]
    at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1702) ~[jackson-core-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:558) ~[jackson-core-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2839) ~[jackson-core-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:1903) ~[jackson-core-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:749) ~[jackson-core-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:3850) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3799) ~[jackson-databind-2.8.10.jar:2.8.10]
    at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:2397) ~[jackson-databind-2.8.10.jar:2.8.10]
    at interv.Web.security.JWTAuthenticationFilter.successfulAuthentication(JWTAuthenticationFilter.java:81) ~[classes/:na]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:240) ~[spring-security-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at interv.Web.service.JWTAutorizationFilter.doFilterInternal(JWTAutorizationFilter.java:43) ~[classes/:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]

JWTAuthorizationFilter.java类

 package interv.Web.service;

import interv.Web.security.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

public class JWTAutorizationFilter extends OncePerRequestFilter{


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {


        httpServletResponse.addHeader("Access-Control-Allow-Origin","*");
        httpServletResponse.addHeader("Access-Control-Allow-Headers", " Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");
        httpServletResponse.addHeader("Access-Control-Expose-Headers",
                "Access-Control-Allow-Origin, Access-Control-Allow-Credentials,Authorization");
        httpServletResponse.addHeader("Access-Control-Allow-Methods","GET,PUT,POST,DELETE");


        String jwtToken = httpServletRequest.getHeader(SecurityConstants.HEADER_STRING);

         if(httpServletRequest.getMethod().equals("OPTIONS")){
             httpServletResponse.setStatus(httpServletResponse.SC_OK);
        }
         else {
             if(jwtToken==null || !jwtToken.startsWith(SecurityConstants.TOKEN_PREFIX)){

                filterChain.doFilter(httpServletRequest,httpServletResponse);

                 return ;
             }

             Claims claims = Jwts.parser()
                    .setSigningKey(SecurityConstants.SECRET)
                    .parseClaimsJws(jwtToken.replace(SecurityConstants.TOKEN_PREFIX,""))
                    .getBody();

            String username = claims.getSubject();
            ArrayList<Map<String,String>> roles = (ArrayList<Map<String,String>>)claims.get("roles");


            Collection<GrantedAuthority> authorities = new ArrayList<>();
            roles.forEach(r->{
                authorities.add(new SimpleGrantedAuthority(r.get("authority")));
             });

            UsernamePasswordAuthenticationToken authenticationToken=
                    new UsernamePasswordAuthenticationToken(username,null,authorities);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(httpServletRequest,httpServletResponse);
         }

        }

    }

2 个答案:

答案 0 :(得分:2)

我不是100%确定如果以下是绝对最好的“弹簧方式”来做到这一点,但你应该遵循这些粗略的步骤:

首先,您需要针对数据存储对用户进行身份验证(例如,使用UsernamePasswordAuthenticationTokenDaoAuthenticationProvider)并构建User对象。在成功验证结束时,您应在User中拥有有效的SecurityContext对象。

之后,创建并签署令牌。我强烈建议使用JWE(有支持此功能的库)。请记住,签名的令牌仅验证其未被篡改:除非主题加密,否则任何人都可以读取令牌。 “创建”令牌意味着您应该序列化User并将其放入令牌有效内容的subject

然后,假设您的客户端将使用某个标头(即x-my-token)向您提供令牌,您需要注册一个Filter来阻止传入的HttpServletRequest并检查是否存在此标题。根据您的要求,您可以选择检查令牌的到期时间(在创建令牌时设置)。

仍然在过滤器中,在解码有效负载并解密您的主题(希望!)后,您将主题反序列化为放置在User中的SecurityContext对象。如果令牌已过期(如果您检查过),或者您无法反序列化有效的User(可能在此期间用户已停用,您可能会检查DB CHECK /用户存储缓存或主题在令牌中以某种方式格式错误),你会抛出异常。成功后,您可以正常继续过滤链。

完成此操作后,您将拥有一个可以检索的有效User(即使用SecurityContext.getPrincipal())并在您的应用程序中使用以执行授权(即使用@PreAuthorize(hasRole(...)),{{1等等。)。

如果您希望仅允许经过身份验证的用户访问您的基础架构,那么您需要做的就是。

如果您想允许匿名访问,我知道有两种方法:

  1. 不要在找不到身份验证标头时抛出异常,只需继续过滤器链。这导致@PreAuthorize(hasPermission(...))中没有有效的User,并且任何访问受保护资源的尝试都将导致异常。

  2. 添加在正常身份验证过滤器后运行的其他SecurityContext组件,即Filter。在其中,如果没有找到存根,则会填充存根AnonymousAuthenticationFilter。此用户应具有User且不存在可能与您系统的其他用户(即ROLE_ANONYMOUS)冲突的其他有效ID值。

  3. 您还可以查看过滤器链层次结构和优先级,因为如果您的身份验证过滤器在错误的时间运行(即过早或过晚),可能会遇到很多问题。

      

    '如何将经过身份验证的用户ID从后端发送到前端,这是有角度的。任何的想法 ?我只使用jwt而不是auth2。

    正如我所提到的,您发送的所有客户端都需要在验证时返回的令牌。

    因此,正常的工作流程如下:

    1. 客户端使用提供的用户凭据执行id = -1,并收到POST /login。客户端存储此令牌。

    2. 客户端会在每次请求时将令牌作为附加标头发送。我并不精通Angular,但this似乎只是一种方法。

    3. 之后,以上述身份验证JWT开头的流程将接管。

    4. 请注意,客户端应该有一些额外的处理能够正常处理令牌过期(如果您检查它)。一种方法是:将明文到期日期和时间添加到令牌有效负载,并在发送请求之前在客户端中进行检查。如果已过期,则提示用户再次登录。

      您最后一个错误可能取决于您创建令牌的以下行:

      Filter

      String jwt = Jwts.builder() .setSubject(springUser.getUsername()) <--- .setExpiration(new Date(System.currentTimeMillis()+SecurityConstants.EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256,SecurityConstants.SECRET) .claim("roles",springUser.getAuthorities()) .compact(); System.out.println("****"+jwt); AppUser app = userRepo.findByUsername(springUser.getUsername()); Long id = app.getId(); ObjectMapper objectMapper = new ObjectMapper(); <--- JsonNode jsonJwt = objectMapper.readTree(jwt); <--- ((ObjectNode)jsonJwt).put("userId", id); <--- 已经包含编码的JWT字符串,这意味着String jwt尝试解析objectMapper.readTree(jwt)(即实际令牌),这将失败。创建后,令牌基本上是不可变的

      此外,在创建令牌时,您只需说eyJhbGciOiJIUzI1NiJ9...。如果您希望其他字段位于主题内,则需要在此处序列化(使用.setSubject(springUser.getUsername()))其他字段。

      这样做的一种方法是简单地创建一个用于保存用户信息的类,即setSubject()用户名MyTokenUser, which you will fill with all required fields (i.e. userId`等。所以:

      ,

      或者,您可能只是序列化 User springUser = (User)authResult.getPrincipal(); AppUser app = userRepo.findByUsername(springUser.getUsername()); Long id = app.getId(); MyTokenUser tokenUser = new MyTokenUser(springUser.getUsername(), id); String jwt = Jwts.builder() .setSubject(tokenUser) .setExpiration(new Date(System.currentTimeMillis()+SecurityConstants.EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256,SecurityConstants.SECRET) .claim("roles",springUser.getAuthorities()) .compact(); response.addHeader(SecurityConstants.HEADER_STRING,SecurityConstants.TOKEN_PREFIX+jwt); ,因为它似乎已经包含了所有必填字段。

答案 1 :(得分:1)

对我来说是正确的,只需执行此类操作,将请求转发给控制器并返回正文。

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
        FilterChain chain, Authentication authentication) throws IOException, ServletException { 
  //Write token in the Response as response.addHeader();
  tokenAuthenticationService.addAuthentication();
  chain.doFilter(request, response); 
}

你添加了响应标题,然后将其转发给控制器,如

@RequestMapping(value = "/login", method = RequestMethod.POST)
     public Output login(
             HttpServletRequest request,@RequestBody UserDto 
      userDto) throws UserServiceException {

         logger.info("Entering login ");
         return Obj;
}

然后除了JWT Token Header之外,你还可以在响应中返回你想要的任何输出。