目录结构
此时完成了添加端点signin到自定义拦截链中
测试应用
此时接口api/hello,仍然是没有权限的,我们添加jwt的支持。
添加JWT
依赖添加
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
开发token生成类
package com.example.demo.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Base64; import java.util.Date; /** * @author 小隐乐乐 * @since 2020/11/8 20:38 */ @Component public class JwtProvider { private String secretKey; private long validityInMilliseconds; @Autowired public JwtProvider(@Value("${security.jwt.token.secret-key}") String secretKey, @Value("${security.jwt.token.expiration}") long milliseconds) { this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); this.validityInMilliseconds = milliseconds; } public String createToken(String username) { //Add the username to the payload Claims claims = Jwts.claims().setSubject(username); //Build the Token Date now = new Date(); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(new Date(now.getTime() + validityInMilliseconds)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } }
create Token方法使用jjwt库创建JWT令牌。用户名就是有效负载的主题。它是使用属性文件中的密钥签名的,并且令牌的有效性也在属性文件中指定。结果令牌将具有Header.Payload.Signature的格式。标头包含类型(JSON Web令牌)和哈希算法(HMAC SHA256)。有效负载又称包含令牌的主题(子),数字日期值(exp)中的到期日期,发布JWT的时间(iat),唯一的JWT标识符(jti)以及由冒号分隔的特定于应用程序的键值对。签名是使用嵌入在应用程序中的密钥的标头和有效负载的哈希值。
配置文件修改
security.jwt.token.secret-key=jwt-token-secret-key-for-encryption security.jwt.token.expiration=1200000
开发web filter
package com.example.demo.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Base64; import java.util.Date; import java.util.Optional; import java.util.function.Function; /** * @author 小隐乐乐 * @since 2020/11/8 20:46 */ public class JwtTokenFilter extends GenericFilterBean { private String secret; private static final String BEARER = "Bearer"; private UserDetailsService userDetailsService; public JwtTokenFilter(UserDetailsService userDetailsService, String secret) { this.userDetailsService = userDetailsService; this.secret = Base64.getEncoder().encodeToString(secret.getBytes()); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { String headerValue = ((HttpServletRequest) req).getHeader("Authorization"); getBearerToken(headerValue).ifPresent(token -> { String username = getClaimFromTokenS((String) token, Claims::getSubject); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (username.equals(userDetails.getUsername()) && !isJwtExpired((String) token)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails( new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) req)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } }); filterChain.doFilter(req, res); } private Optional getBearerToken(String headerVal) { if (headerVal != null && headerVal.startsWith(BEARER)) { return Optional.of(headerVal.replace(BEARER, "").trim()); } return Optional.empty(); } private Date getClaimFromToken(String token, Function<Claims, Date> claimsResolver) { final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); return claimsResolver.apply(claims); } private String getClaimFromTokenS(String token, Function<Claims, String> claimsResolver) { final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); return claimsResolver.apply(claims); } private boolean isJwtExpired(String token) { Date expirationDate = getClaimFromToken(token, Claims::getExpiration); return expirationDate.before(new Date()); } }
这与Java EE中的Web过滤器相同。此Web筛选器检查令牌是否已过期(即Jwt已过期)。它解析有效负载,获取来自令牌的Claims。它将剥离承载字符串,该字符串是授权标头的值。如果令牌通过了所有检查,则do Filter方法将配置Spring安全性以在上下文中手动设置身份验证。我们指定当前用户已通过身份验证,以使其成功通过。
接下来是在Web安全配置类中添加一些行。
WebSecurityConfiguration改造
package com.example.demo.config; import com.example.demo.jwt.JwtTokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * @author 小隐乐乐 * @since 2020/11/8 20:20 */ @Configuration public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Value("${security.jwt.token.secret-key}") private String secret; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/signin").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(new JwtTokenFilter(userDetailsService, secret), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean @Override public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } }
接口类改造
package com.example.demo.controller; import com.example.demo.dto.SigninDto; import com.example.demo.jwt.JwtProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** * @author 小隐乐乐 * @since 2020/11/8 19:44 */ @RestController @RequestMapping("/api") public class HelloController { @Autowired private JwtProvider jwtProvider; @Autowired private AuthenticationManager authenticationManager; @GetMapping("/hello") public String hello() { return "hello guys"; } @PostMapping("/signin") public String signIn(@RequestBody @Valid SigninDto signInDto) { try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(signInDto.getUsername(), signInDto.getPassword())); return jwtProvider.createToken(signInDto.getUsername()); } catch (AuthenticationException e) { System.out.println("Log in failed for user, " + signInDto.getUsername()); } return ""; } }
测试应用
signin接口调用获取token
hello接口调用携带token
总结
Spring Boot Security和JWT Hello World示例已完成。总而言之,当登录身份验证通过时,我们将创建一个JSON Web令牌并将其返回给调用方。然后,调用方将JWT放置在标头中,并在其后续的GET请求中使用授权密钥。 Web过滤器检查令牌的有效性。如果有效,Web过滤器将使其通过过滤器链并返回“ Hello world”。