文章目录
jwt结构
一个 JWT 实际上就是一个字符串,它由三部分组成:头部、载荷与签名。中间用点 . 分隔成三个部分。
头部 / header
header 由两部分组成: token 的类型 JWT 和算法名称:HMAC、SHA256、RSA
载荷 / Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 指定七个默认字段供选择。
除了默认字段之外,你完全可以添加自己想要的任何字段,一般用户登录成功后,就将用户信息存放在这里
签名 / Signature
签名部分是对上面的 头部、载荷 两部分数据进行的数据签名
为了保证数据不被篡改,则需要指定一个密钥,而这个密钥一般只有你知道,并且存放在服务端
token认证过程
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器
springboot实现jwt+token认证
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
在config层中建立WebSecurityConfig加入自定义拦截器与自定义异常处理
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//token的验证方式不需要开启csrf的防护
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
//设置无状态的连接,即不创建session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 当前的url允许进行匿名访问,即不需要身份认证
.antMatchers(
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
//配置swagger界面的匿名访问
.antMatchers("/swagger-ui.html",
"/swagger-ui/*",
"/swagger-resources/**",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**").permitAll()
.antMatchers("/data/**").permitAll()
.antMatchers("/image/**").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers("/configuration/security").permitAll()
//配置允许匿名访问的路径
.antMatchers("/nb5/**").permitAll()
.antMatchers("/webapp/**").permitAll()
.antMatchers("/home/**").permitAll()
.antMatchers("/nb4/**").permitAll()
.antMatchers("/shale_porosity/user/log").permitAll()
.antMatchers("/shale_porosity/upload/**").permitAll()
.antMatchers("/shale_porosity/download/**").permitAll()
.antMatchers("/shale_porosity/task/**").permitAll()
.antMatchers(HttpMethod.POST,"/wx_official_accounts/record-time").permitAll()
.anyRequest().authenticated(); //除了上面请求外都需要权限认证
//添加jwt filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
httpSecurity.headers().cacheControl();
httpSecurity.headers().frameOptions().sameOrigin();
httpSecurity.headers().frameOptions().disable();
}
}
在security中建立JwtAuthenticationTokenFilter拦截器与JwtAuthenticationEntryPoint异常处理
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
private String tokenHeader = "Authorization";
@Override
//允许请求头 设置
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
log.info("浏览器的请求预处理");
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
// httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization,token,Cookie");
} else {
String requestUrl = httpServletRequest.getRequestURI();
log.info("requestURI: {}", requestUrl);
String authtoken = httpServletRequest.getHeader(this.tokenHeader);
System.out.println(authtoken);
String workNum = jwtTokenUtil.getUsernameFromToken(authtoken);
//取出username (workNum)
log.info("checking authentication for user " + workNum);
if (workNum != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//请求头
log.info("token中的username不为空,Context中的authentication为空时,进行token验证");
UserDetails userDetails = this.userDetailsService.loadUserByUsername(workNum);
log.info("加载UserDetails:{}", userDetails.getUsername());
if (jwtTokenUtil.validateToken(authtoken, userDetails)) {
//验证token中的信息,调用了tokenutils的方法
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(null, null, null);
System.out.println(authenticationToken);
log.info("authenticated user" + workNum + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
R result = R.error(ResultEnum.AUTHENTICATION_ERROR);
log.info("需要身份认证:{}", result);
httpServletResponse.getWriter().append(JSON.toJSONString(result));
}
}
期间需要注意的一些事情
登录
自定义登录接口
调用方法进行认证 如果认证通过生成jwt返回前端,并将用户信息存入 SecurityContextHolder中,SecurityContextHolder用Threadlocal方式实现,用于存储线程中独有的信息。
校验:
定义Jwt认证过滤器
获取token
解析token获取其中的userid
判断SecurityContextHolder中是否还存在值,如果不存在,通过userid查找数据库中的user,并判断token是否过期,如果通过认证,会将新获取的用户信息存入SecurityContextHolder中,后序需要用到用户信息可直接在里面读取。
权限判断
在config里面创建 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthRoleInterceptor authRoleInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//除了/user其他都需要输入返回的 token 判断权限Role
registry.addInterceptor(authRoleInterceptor)
.excludePathPatterns("/user/**");
}
}
在security中创建权限拦截器
@Slf4j
@Service
public class AuthRoleInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserService userService;
@Override
//HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,
// 通过这个对象提供的方法,可以获得客户端请求的所有信息。
//在调用逻辑处理方法前面进行判断权限 preHandle获取请求信息判断用户的权限
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(".....................");
response.setCharacterEncoding("UTF-8");
//此方法同时设置服务器和客户端都是用UTF-8字符集,还设置了响应头
response.setContentType("text/html;charset=utf-8");
String json = JSON.toJSONString(R.error(ResultEnum.AUTHENTICATION_ERROR));
//获取当前登录的用户信息
User user = userService.getCurrentUser();
if (user == null) {
return true;
}
log.info("============执行权限验证============");
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RoleContro roleContro = handlerMethod.getMethodAnnotation(RoleContro.class);
if (roleContro == null) {
return true;
}
Integer roleValue = roleContro.role().getValue();
Integer userValue = user.getRole();
log.info("RoleValue:{},userRole:{}", roleValue, userValue);
if (userValue >= roleValue) {
return true;
} else {
json = JSON.toJSONString(R.error(ResultEnum.PERMISSION_DENNY));
log.info("============权限不足===============");
}
}
response.getWriter().append(json);
return false;
}
}