JWT是Json Web Token的缩写。它是基于RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据使用了数字签名,所以是可信任和安全的。
JWT使用 . 分隔成三部分:
Header例子:
此json将被Base64Url编码(相等于明文)以形成JWT的第一部分
{
'typ': 'JWT',
'alg': 'HS256'
}
Payload例子:
对有效负载进行Base64Url编码,以形成JWT的第二部分
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature例子
签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
1)pom.xml文件中添加相关依赖
io.jsonwebtoken
jjwt
0.9.1
2)JwtAuthenticationTokenFilter类
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final static Logger logger = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Resource
private UserDetailsService userDetailsService;
@Resource
private JwtTokenUtils jwtTokenUtils;
@Resource
private UserService userService;
private String tokenHeader = "Authorization";
private String tokenHead = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("进入到 JWT Filter的doFilterInternal方法中");
//先从url中取token
String authToken = request.getParameter("token");
String authHeader = request.getHeader(this.tokenHeader);
if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(tokenHead)) {
//如果header中存在token,则覆盖掉url中的token
authToken = authHeader.substring(tokenHead.length()); // "Bearer "之后的内容
}
if (StringUtils.isNotBlank(authToken)) {
String username = jwtTokenUtils.getUsernameFromToken(authToken);
logger.info("checking authentication {}", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//从已有的user缓存中取了出user信息
User user = userService.findUserByUsername(username);
//检查token是否有效
if (jwtTokenUtils.validateToken(authToken, user)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//设置用户登录状态
logger.info("authenticated user {}, setting security context",username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
3)application.yml
config:
jwt:
# 加密密钥
secret: iwqjhda8232bjgh432[cicada-smile]
# token有效时长
expire: 3600
4)JwtTokenUtils类
@Component
public class JwtTokenUtils {
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_ID = "id";
private static final String CLAIM_KEY_CREATED = "created";
private static final String CLAIM_KEY_ROLES = "roles";
@Value("${config.jwt.secret}")
private String secret;
@Value("${config.jwt.expire}")
private int expiration; //过期时长,单位为秒,可以通过配置写入
public String getUsernameFromToken(String token) {
String username;
try {
username =getClaimsFromToken(token).getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(User userDetails) {
Map claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
claims.put(CLAIM_KEY_ID, userDetails.getId());
claims.put(CLAIM_KEY_ROLES, userDetails.getAuthorities());
return generateToken(claims);
}
public String generateToken(Map claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean canTokenBeRefreshed(String token) {
return !isTokenExpired(token);
}
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getCreatedDateFromToken(token);
return (
username.equals(user.getUsername())
&& isTokenExpired(token)==false);
}
}
5)MySecurityConfig配置文件
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
MyUserDetailService myUserDetailService;
//Control+O 打开重写方法
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("进入到 MySecurityConfig的configure 方法中");
//super.configure(http);
/*Spring Security 的默认构造器:
通过调用authorizeRequests()和 anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic(); //弹出一个输入用户名、密码的登录框
////////////////
“/shop/hello” 和 “/shop/order” 这两个路径必须进过认证,并且 “/shop/order” 必须是 post 请求的方式.
对于其他的请求,我们都是 .anyRequest().permitAll() ;都放行.
http.authorizeRequests()
.antMatchers("/shop/hello").authenticated()
.antMatchers(HttpMethod.POST,"/shop/order").authenticated()
.anyRequest().permitAll();
antMatchers()方法所使用的路径可能会包括Ant风格的通配符,而regexMatchers()方法则能够接受正则表达式来定义请求路径。
*/
// 基于token,所以不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// /**代表所有的请求
http.authorizeRequests()//方法表示开启了认证规则配置;定义哪些url需要保护,哪些url不需要保护;
.antMatchers("/api/**").permitAll()//定义不需要认证就可以访问
// .antMatchers("/session/**").permitAll()//定义不需要认证就可以访问
// .antMatchers("/component/**").hasAuthority("ROLE_ADMIN")
//// .antMatchers("/home/**").hasAnyAuthority("ROLE_DEV_APPLICATION","ROLE_ADMIN","ROLE_DEV_TERMINAL_IOS")
.anyRequest().authenticated();////其他的路径都是登录后即可访问
// .and().formLogin().loginPage("/")
//
// //在successHandler中,使用response返回登录成功的json即可,切记不可以使用defaultSuccessUrl,defaultSuccessUrl是只登录成功后重定向的页面,failureHandler也是由于相同的原因不使用failureUrl。
// .loginProcessingUrl("/login").successHandler(
// new AuthenticationSuccessHandler(){
// @Override
// public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// System.out.println(authentication.getDetails());
// httpServletResponse.setContentType("application/json;charset=utf-8");
// PrintWriter out = httpServletResponse.getWriter();
// out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
// out.flush();
// out.close();
// }
// }).failureHandler(
// new AuthenticationFailureHandler() {
// @Override
// public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
// httpServletResponse.setContentType("application/json;charset=utf-8");
// PrintWriter out = httpServletResponse.getWriter();
// out.write("{\"status\":\"failed\",\"msg\":\"登录失败\"}");
// out.flush();
// out.close();
// }
// });
//http.logout()开启自动配置的注销功能
//1) 访问/logout 表示用户注销,清空session
//2) 注销成功会返回/login?logout 页面
//3) logoutSuccessfulUrl 改变2)的设置
http.logout().logoutSuccessUrl("/login");
http.sessionManagement().invalidSessionUrl("/login");
http.rememberMe().rememberMeParameter("remember");
// .usernameParameter("username").passwordParameter("password").defaultSuccessUrl("/");////定义当需要用户登录时候,转到的登录页面
// http.headers().frameOptions().disable();
// .antMatchers("/level2/**").hasRole("VIP2")
// .antMatchers("/level3/**").hasRole("VIP3");
//开启自动配置的登录功能。如果没有登录,没有权限就会来到登录页面
//1:/login来到登录页
//2:重定向/login?error表示登录失败
//3:更多详细规定
//http.formLogin().defaultSuccessUrl("/user/login.html");
/* iframe */
http.headers().frameOptions().sameOrigin(); // 运行同一个域名中的任何请求
http.csrf().disable(); // 默认是启用的,需要禁用CSRF保护(当不使用cookie时可以禁用csrf)
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
http.headers().cacheControl();
}
//定制请求的认证规则
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("进入到 configureGlobal 方法中");
System.out.println("auth.userDetailsService(myUserDetailService):" + auth.userDetailsService(myUserDetailService));
//1)获取内存中的用户名和密码
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//// .withUser("1").password(new BCryptPasswordEncoder().encode("1")).roles("USER");
// .withUser("1").password(new BCryptPasswordEncoder().encode("1")).authorities("ROLE_TEST");
// auth.authenticationProvider(authenticationProvider());
// auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
//2)获取数据库中的用户名和密码
auth.userDetailsService(myUserDetailService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}
/*
通过AuthenticationProvider方式获取
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
System.out.println("进入到 authenticationProvider 方法中");
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(myUserDetailService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
* 密码生成策略
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
System.out.println("进入到 passwordEncoder 方法中");
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
System.out.println("进入到 authenticationTokenFilterBean 方法中");
return new JwtAuthenticationTokenFilter();
}
}