「这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战」
确保Web应用程序的安全是一个固有的复杂命题。Spring Security为Java开发者提供了一个强大的框架来满足这一需求,但这一强大的工具伴随着一个陡峭的学习曲线。
本文简要介绍了用Spring Security保护REST API背后的基本组件。我们将建立一个简单的应用,使用JSON Web Token(JWT)来存储用户的信息。
JWT由于其简单性和紧凑性,正在迅速成为持有授权信息的标准方法。
认证过滤器 TokenAuthenticationFilter.java
TokenAuthenticationFilter
负责检查进入受保护URL
的请求。这项工作在清单4
中完成。
清单4.过滤器的逻辑
@Override
public Authentication attemptAuthentication(final HttpServletRequest request,
final HttpServletResponse response) {
final String param = ofNullable(request.getHeader(AUTHORIZATION)).orElse(request.getParameter("t"));
final String token = ofNullable(param).map(value -> removeStart(value, "Bearer"))
.map(String::trim).orElseThrow(() -> new BadCredentialsException("No Token Found!"));
final Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
@Override
protected void successfulAuthentication(final HttpServletRequest request,
final HttpServletResponse response, final FilterChain chain,
final Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
复制代码
基本上,过滤器从授权头中提取token(由前端JS
发送的那个)。如果它不在那里,就会产生一个异常。如果它在那里,它就会被移交给认证管理器,在那里它最终会被你刚才在SecurityConfig
中看到的TokenAuthenticationProvider
处理。
检查token TokenAuthenticationProvider.java
TokenAuthenticationProvider
负责根据auth
token恢复用户。它只有一个方法,将其工作委托给UserAuthenticationService
,如清单5
中所示。
清单5.TokenAuthenticationProvider.retrieveUser()
@Autowired
UserAuthenticationService auth;
//...
@Override
protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
final Object token = authentication.getCredentials();
return Optional.ofNullable(token).map(String::valueOf).flatMap(auth::findByToken)
.orElseThrow(() -> new UsernameNotFoundException("Couldn't find user: " + token));
}
复制代码
如果用户为空,就会产生一个异常。
UserAuthenticationService.java 和TokenAuthenticationService.java
TokenAuthenticationService
是将被自动连接到TokenAuthenticationProvider
的实现。它提供了用于检索用户的findByToken
方法
TokenAuthenticationService
也是登录流程与认证流程结合的地方。它提供了UserController
使用的login()
方法。在清单6
中可以看到这两个方法。
清单6.TokenAuthenticationService方法
@Autowired
TokenService tokenService;
@Autowired
UserService users;
@Override
public Optional<String> login(final String username, final String password) {
return users
.findByUsername(username)
.filter(user -> Objects.equals(password, user.getPassword()))
.map(user -> tokenService.newToken(ImmutableMap.of("username", username)));
}
@Override
public Optional<User> findByToken(final String token) {
System.out.println("$$$$$$$$$$$$$$$$$$$$ token: " + token);
return Optional
.of(tokenService.verify(token))
.map(map -> map.get("username"))
.flatMap(users::findByUsername);
}
复制代码
两个方法--findByToken
和login--
都依赖于TokenService
和UserService
。 findByToken
需要一个token,然后使用tokenService
来验证其有效性。如果token是好的,findByToken
使用UserService
来获取实际的用户对象。
login
的做法正好相反。它需要一个用户名,用userService
抓取用户,验证密码是否匹配,然后用tokenService
来创建token。
TokenService.java和JWTTokenService.java
JWTTokenService是处理实际JWT
token的地方。它依靠JJWT库来完成工作,如清单7
所示。
清单7.JWTTokenService
JWTTokenService() {
super();
this.issuer = requireNonNull("infoworld");
this.secretKey = BASE64.encode("www.infoworld.com");
}
public String newToken(final Map<String, String> attributes) {
final DateTime now = DateTime.now();
final Claims claims = Jwts.claims().setIssuer(issuer).setIssuedAt(now.toDate());
claims.putAll(attributes);
return Jwts.builder().setClaims(claims).signWith(HS256, secretKey).compressWith(COMPRESSION_CODEC)
.compact();
}
@Override
public Map<String, String> verify(final String token) {
final JwtParser parser = Jwts.parser().requireIssuer(issuer).setClock(this).setSigningKey(secretKey);
return parseClaims(() -> parser.parseClaimsJws(token).getBody());
}
private static Map<String, String> parseClaims(final Supplier<Claims> toClaims) {
try {
final Claims claims = toClaims.get();
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (final Map.Entry<String, Object> e: claims.entrySet()) {
builder.put(e.getKey(), String.valueOf(e.getValue()));
}
return builder.build();
} catch (final IllegalArgumentException | JwtException e) {
return ImmutableMap.of();
}
}
复制代码
JJWT
库使创建、解析和验证JWT
token变得相当容易。newToken()
方法使用Jwts.claim()
来设置几个标准的claim
(发行者和发行地点)以及作为参数传入的任何其他claim
。在登录的情况下,这将包含用户的名字。这意味着用户名可以在以后的认证过程中被反序列化。在这一点上,应用程序也可以添加其他索赔,如角色或明确的权限类型。
感谢观看,如果您有兴趣,可以关注一下我,方便查看后续文章,一起学习,共同进步,不胜感激!