建设网站时 首先要解决两个问题 一是什么wordpress front-page.php
JWT Token
JWT字符串,字符之间通过"."分隔符分为三个子串。 每一个子串表示了一个功能块,总共有以下三个部分:JWT 头、有效载荷和签名。
头部:JWT 头部分是一个描述 JWT 元数据的 JSON 对象,主要设置一些规范信息,签名部分的编码格式就在头部中声明。
载荷:token中存放有效信息的部分,是 JWT 的主体内容部分,是一个 JSON 对象,包含需要传递的数据。比如用户名,用户角色,过期时间等,但是不要放密码,会泄露。
指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID 用于标识该 JWT
1. 基本流程
认证:通过自定义一个用户名和密码登录的过滤器JwtTokenLoginFilter,登录成功后生成JWT并返回。客户端调用接口需要传JWT Token,通过过滤器AuthJwtTokenFilter对请求拦截并验证Token的合法性,已经是否过期。
授权:通过使用注解(类似@PreAuthorize)或者配置(.antMatchers("/hellouser").hasAuthority("query"))对接口配置权限。authenticationEntryPoint是配置认证异常,accessDeniedHandler是配置权限异常。
2.JWT工具类
@Component
@Data
public class JwtUtils {//秘钥private String secret = "mysecret";// 过期时间 毫秒private Long expiration = 120l * 1000;public String createToken(String userName) {return Jwts.builder().setSubject(userName)//生成时间.setIssuedAt(new Date())//过期时间.setExpiration(new Date(System.currentTimeMillis() + expiration)).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String generateToken(Map<String, Object> claims, Long expiration) {Date expirationDate = new Date(System.currentTimeMillis() + expiration);return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 从token中解析出数据** @param token 令牌* @return*/private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {throw e;}return claims;}/*** 从令牌中获取用户名** @param token 令牌* @return 用户名*/public String getUsernameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username = claims.getSubject();} catch (Exception e) {throw e;}return username;}/*** 判断令牌是否过期** @param token 令牌* @return 是否过期*/public Boolean isTokenExpired(String token) {try {Claims claims = getClaimsFromToken(token);Date expiration = claims.getExpiration();return expiration.before(new Date());} catch (Exception e) {return true;}}/*** 刷新令牌** @param token 原令牌* @return 新令牌*/public String refreshToken(String token) {String refreshedToken;try {Claims claims = getClaimsFromToken(token);claims.put(Claims.ISSUED_AT, new Date());refreshedToken = generateToken(claims, 2 * expiration);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}/*** 验证令牌** @param token 令牌* @param userInputName 用户* @return 是否有效*/public Boolean validateToken(String token, String userInputName) {String username = getUsernameFromToken(token);return (username.equals(userInputName) && !isTokenExpired(token));}
}
2.登录过滤器
public class JwtTokenLoginFilter extends AbstractAuthenticationProcessingFilter {public JwtTokenLoginFilter(String filterProcessesUrl) {super(new AntPathRequestMatcher(filterProcessesUrl, HttpMethod.POST.name()));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//获取表单提交数据String username = request.getParameter("username");String password = request.getParameter("password");//封装到token中提交UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,password);return getAuthenticationManager().authenticate(authRequest);}
}
3.认证过滤器
public class AuthJwtTokenFilter extends OncePerRequestFilter {@AutowiredJwtUtils jwtUtils;@AutowiredMyUserDetailsService myUserDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {String token = request.getHeader("Authorization");/*** token存在则校验token* 1. token是否存在* 2. token存在:* 2.1 校验token中的用户名是否失效*/String username = "";if (!StringUtils.isEmpty(token)) {try {username = jwtUtils.getUsernameFromToken(token);//SecurityContextHolder.getContext().getAuthentication()==null 未认证则为trueif (!StringUtils.isEmpty(username) && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);//如果token有效if (jwtUtils.validateToken(token, userDetails.getUsername())) {// 将用户信息存入 authentication,方便后续校验UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 将 authentication 存入 ThreadLocal,方便后续获取用户信息SecurityContextHolder.getContext().setAuthentication(authentication);}}} catch (ExpiredJwtException e) {response.setStatus(HttpStatus.FORBIDDEN.value());ResponseUtils.result(response, "token已过期,重新登录!");return;}}//继续执行下一个过滤器chain.doFilter(request, response);}
}
4.实现UserDetailsService
@Slf4j
public class MyUserDetailsService implements UserDetailsService {Map<String, String> user = new HashMap();Map<String, List<String>> userRole = new HashMap<>();{user.put("admin", "111111");user.put("zs", "111111");user.put("noaccess", "111111");//hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这// 里会自动给传入的字符串前缀(默认是ROLE_ ),// 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,// 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。// 在SecurityExpressionRoot 类中hasAuthority 和 hasRole// 最终都是调用了 hasAnyAuthorityName 方法。userRole.put("admin", Arrays.asList("ROLE_admin"));userRole.put("zs", Arrays.asList("query"));}@AutowiredPasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {log.info("MyUserDetailsService.loadUserByUsername 开始");if (!user.containsKey(username)) {throw new UsernameNotFoundException("username is not exists");}List<SimpleGrantedAuthority> authorities = new ArrayList<>();if (userRole.containsKey(username)) {authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row)).collect(Collectors.toList());}return new User(username, passwordEncoder.encode(user.get(username)), authorities);}
}
5.登录成功和失败处理的Handler
@Component
public class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {if (exception instanceof BadCredentialsException) {ResponseUtils.result(response, "用户名或密码不正确!");}ResponseUtils.result(response, "登录失败");}
}
@Component
public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Autowiredprivate JwtUtils jwtTokenUtil;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {UserDetails userDetails = (UserDetails) authentication.getPrincipal();SecurityContextHolder.getContext().setAuthentication(authentication);//生成令牌String accessToken = jwtTokenUtil.createToken(userDetails.getUsername());//生成刷新令牌,如果accessToken令牌失效,则使用refreshToken重新获取令牌(refreshToken过期时间必须大于accessToken)String refreshToken = jwtTokenUtil.refreshToken(accessToken);Map<String, Object> resultData = new HashMap<>();Map<String, Object> tokenData = new HashMap<>();tokenData.put("accessToken", accessToken);tokenData.put("refreshToken", refreshToken);resultData.put("msg", "登录成功");resultData.put("token", tokenData);ResponseUtils.result(response, resultData);}
}
6.Security配置类
@Configuration
public class JwtTokenLoginConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@AutowiredMyUserDetailsService myUserDetailsService;@AutowiredLoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;@AutowiredLoginAuthenticationFailureHandler loginAuthenticationFailureHandler;@AutowiredPasswordEncoder passwordEncoder;@Overridepublic void configure(HttpSecurity httpSecurity) throws Exception {JwtTokenLoginFilter filter = new JwtTokenLoginFilter("/mylogin");filter.setAuthenticationManager(httpSecurity.getSharedObject(AuthenticationManager.class));//认证成功处理器filter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);//认证失败处理器filter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);httpSecurity.authenticationProvider(daoAuthenticationProvider());//将过滤器添加到UsernamePasswordAuthenticationFilter之前执行httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);}@BeanDaoAuthenticationProvider daoAuthenticationProvider() {//使用DaoAuthenticationProviderDaoAuthenticationProvider provider = new DaoAuthenticationProvider();//设置userDetailServiceprovider.setUserDetailsService(myUserDetailsService);//设置加密算法provider.setPasswordEncoder(passwordEncoder);return provider;}
}
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredJwtTokenLoginConfig jwtTokenLoginConfig;@AutowiredAuthJwtTokenFilter authJwtTokenFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {//关闭csrfhttp.csrf().disable()//禁用表单登录.formLogin().disable()//应用登录过滤器的配置,配置分离.apply(jwtTokenLoginConfig);http.authorizeRequests().antMatchers("/index", "/refreshToken").permitAll().anyRequest().authenticated().and()//禁用session,JWT校验不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//处理异常情况:认证和权限http.exceptionHandling()//认证未通过,不允许访问异常处理器--认证异常.authenticationEntryPoint(MyWebSecurityConfig::MyAuthenticationEntryPoint)//认证通过,但是没权限处理器--授权异常.accessDeniedHandler(MyWebSecurityConfig::MyAccessDeniedHandler);http.addFilterBefore(authJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);}//public static void MyAuthenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {response.setStatus(HttpStatus.FORBIDDEN.value());ResponseUtils.result(response, "无权限访问,请先登录!");}public static void MyAccessDeniedHandler(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {response.setStatus(HttpStatus.UNAUTHORIZED.value());ResponseUtils.result(response, "无权限!");}
}
7.测试Controller
· index接口可以命令访问· loginstatus接口需要登录成功后才能访问,因为未设置权限,所以不需要授权就能访问。· hello****的接口登录认证成功后,并且需要想要的权限才能访问。
授权的注解,一定要开启@EnableGlobalMethodSecurity(prePostEnabled = true)
@RestController
public class HelloController {@GetMapping("/index")public String index() {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("<div><a href='/hello'>hello</a></div>");stringBuffer.append("<div><a href='/helloadmin'>helloadmin</a></div>");stringBuffer.append("<div><a href='/hellouser'>hellouser</a></div>");stringBuffer.append("<div><a href='/loginstatus'>loginalert</a></div>");return stringBuffer.toString();}/*** 不需要权限,只需要认证即可访问* @return*/@GetMapping("/loginstatus")public String loginalert() {return "已经登录成功了";}@GetMapping("/hello")@PreAuthorize("hasRole('admin') OR hasAuthority('query')")public String hello() {return "HelloController hello";}@GetMapping("/helloadmin")@PreAuthorize("hasRole('admin')")public String helloAdmin() {return "HelloController helloAdmin";}@GetMapping("/hellouser")@PreAuthorize("hasRole('admin') OR hasAuthority('query')")public String helloUser() {return "HelloController helloUser";}
}
@RestController
public class LoginController {@AutowiredJwtUtils jwtUtils;/*** 刷新令牌** @return*/@PostMapping("/refreshToken")public ResponseEntity<Object> refreshToken(HttpServletRequest request) {//从请求头中获取refreshTokenString oldRefreshToken = request.getHeader("Authorization");//校验refreshToken,如果令牌没有过期if (jwtUtils.isTokenExpired(oldRefreshToken)) {return new ResponseEntity<>("刷新令牌已过期,请重新登录!", HttpStatus.ACCEPTED);}//解析refreshTokenString username = jwtUtils.getUsernameFromToken(oldRefreshToken);//生成新的accessTokenString newAccessToken = jwtUtils.createToken(username);String newRefreshToken = jwtUtils.refreshToken(newAccessToken);Map<String, Object> resultData = new HashMap<>();Map<String, Object> tokenData = new HashMap<>();tokenData.put("accessToken", newAccessToken);tokenData.put("refreshToken", newRefreshToken);resultData.put("msg", "刷新令牌成功");resultData.put("token", tokenData);return ResponseEntity.ok(resultData);}
}
8.其他
public class ResponseUtils {public static void result(HttpServletResponse response, Object msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream out = response.getOutputStream();ObjectMapper objectMapper = new ObjectMapper();out.write(objectMapper.writeValueAsString(msg).getBytes("UTF-8"));out.flush();out.close();}
}
@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJwtProgram {public static void main(String[] args) {SpringApplication.run(SecurityJwtProgram.class, args);}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic MyUserDetailsService myUserDetailsService() {return new MyUserDetailsService();}// 自定义的Jwt Token校验过滤器@Beanpublic AuthJwtTokenFilter authJwtTokenFilter() {return new AuthJwtTokenFilter();}
}
9.测试
在MyUserDetailsService中分别创建了三个账号,admin,zs, noaccess,所以对这三个账号分别验证。
mylogin接口:
loginstatus接口:
hello接口:
helloadmin接口:
hellouser接口:
参考:
SpringBoot+SpringSecurity+JWT实现认证和授权_springboot security jwt-CSDN博客