大家好!
我正在开发一个带有弹簧启动(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);
}
}
}
答案 0 :(得分:2)
我不是100%确定如果以下是绝对最好的“弹簧方式”来做到这一点,但你应该遵循这些粗略的步骤:
首先,您需要针对数据存储对用户进行身份验证(例如,使用UsernamePasswordAuthenticationToken
和DaoAuthenticationProvider
)并构建User
对象。在成功验证结束时,您应在User
中拥有有效的SecurityContext
对象。
之后,创建并签署令牌。我强烈建议使用JWE(有支持此功能的库)。请记住,签名的令牌仅验证其未被篡改:除非主题加密,否则任何人都可以读取令牌。 “创建”令牌意味着您应该序列化User
并将其放入令牌有效内容的subject
。
然后,假设您的客户端将使用某个标头(即x-my-token
)向您提供令牌,您需要注册一个Filter
来阻止传入的HttpServletRequest
并检查是否存在此标题。根据您的要求,您可以选择检查令牌的到期时间(在创建令牌时设置)。
仍然在过滤器中,在解码有效负载并解密您的主题(希望!)后,您将主题反序列化为放置在User
中的SecurityContext
对象。如果令牌已过期(如果您检查过),或者您无法反序列化有效的User
(可能在此期间用户已停用,您可能会检查DB CHECK /用户存储缓存或主题在令牌中以某种方式格式错误),你会抛出异常。成功后,您可以正常继续过滤链。
完成此操作后,您将拥有一个可以检索的有效User
(即使用SecurityContext.getPrincipal()
)并在您的应用程序中使用以执行授权(即使用@PreAuthorize(hasRole(...))
,{{1等等。)。
如果您希望仅允许经过身份验证的用户访问您的基础架构,那么您需要做的就是。
如果您想允许匿名访问,我知道有两种方法:
不要在找不到身份验证标头时抛出异常,只需继续过滤器链。这导致@PreAuthorize(hasPermission(...))
中没有有效的User
,并且任何访问受保护资源的尝试都将导致异常。
添加在正常身份验证过滤器后运行的其他SecurityContext
组件,即Filter
。在其中,如果没有找到存根,则会填充存根AnonymousAuthenticationFilter
。此用户应具有User
且不存在可能与您系统的其他用户(即ROLE_ANONYMOUS
)冲突的其他有效ID值。
您还可以查看过滤器链层次结构和优先级,因为如果您的身份验证过滤器在错误的时间运行(即过早或过晚),可能会遇到很多问题。
'如何将经过身份验证的用户ID从后端发送到前端,这是有角度的。任何的想法 ?我只使用jwt而不是auth2。
正如我所提到的,您发送的所有客户端都需要在验证时返回的令牌。
因此,正常的工作流程如下:
客户端使用提供的用户凭据执行id = -1
,并收到POST /login
。客户端存储此令牌。
客户端会在每次请求时将令牌作为附加标头发送。我并不精通Angular,但this似乎只是一种方法。
之后,以上述身份验证JWT
开头的流程将接管。
请注意,客户端应该有一些额外的处理能够正常处理令牌过期(如果您检查它)。一种方法是:将明文到期日期和时间添加到令牌有效负载,并在发送请求之前在客户端中进行检查。如果已过期,则提示用户再次登录。
您最后一个错误可能取决于您创建令牌的以下行:
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之外,你还可以在响应中返回你想要的任何输出。