赞
踩
如何你能看到这边文章,那我觉得我也不需要多废话,文章里面的东西你自然可以看懂。此篇文章只介绍整合过程,不介绍原理,适合懂原理想快速搭建环境的人儿们。
先晒一下项目目录:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <dependencies> <!-- jwt相关依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.10.8</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.10.8</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.10.8</version> </dependency> <!-- spring security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- spring web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> </dependencies>
server:
port: 7100
jwt:
#http请求头
header: Authorization
#token起始标识
start-with: Bearer
#秘钥
secret-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKiuOGSVzac1HH5x3cboD0KapCayatw4G6W59E9Vez6fwix2g5hUFmzRknoZiDREuAXkVi1MqAiQ7Wf8MqqPDvsCAwEAAQ==
#过期时间 单位/秒
validate-second: 43200
/** * @author : LCheng * @date : 2020-11-26 16:52 * description : jwt配置参数 */ @Data @ConfigurationProperties(prefix = "jwt") @Configuration public class JwtProperties { /** * http请求头 */ private String header; /** * token起始标识 */ private String startWith; /** * token秘钥 */ private String secretKey; /** * token过期时间 单位/秒 */ private Long validateSecond; }
SecurityAccessDeniedHandler 用来处理无访问权限的请求,这里只是返回403的操作,可以根据系统来开发相应的功能
/**
* @author : LCheng
* @date : 2020-11-26 16:56
* description : 无访问权限处理类
*/
@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//根据系统要求开发功能代码
response.sendError(HttpServletResponse.SC_FORBIDDEN, "您的权限不足。");
}
}
SecurityAuthenticationEntryPoint 用来处理认证失败的请求,这里只是返回401的操作,可以根据系统来开发相应的功能
/**
* @author : LCheng
* @date : 2020-11-26 16:58
* description : 认证失败处理类
*/
@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
//根据系统要求开发功能代码
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "认证失败。");
}
}
/** * @author : LCheng * @date : 2020-11-26 16:59 * description : jwt工具 */ @Component @Slf4j public class JwtUtil { @Autowired private JwtProperties jwtProperties; public static final String USER_KEY = "user"; /** * 从request中获取token * * @param request * @return {@link String} * @author LCheng * @date 2020/11/26 17:15 */ public String getToken(HttpServletRequest request) { String token = ""; String bearerToken = request.getHeader(jwtProperties.getHeader()); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtProperties.getStartWith())) { token = bearerToken.substring(jwtProperties.getStartWith().length()); } return token; } /** * 根据token获取AuthenticationToken * * @param token * @return {@link UsernamePasswordAuthenticationToken} * @author LCheng * @date 2020/11/26 18:16 */ public UsernamePasswordAuthenticationToken getAuthentication(String token) { Claims claims = validate(token); if (claims == null) { return null; } HashMap map = (HashMap) claims.get(USER_KEY); Collection<? extends GrantedAuthority> authorities = ((List<Map<String, String>>) map.get("authorities")).stream() .map(a -> new SimpleGrantedAuthority(a.get("authority"))) .collect(Collectors.toList()); User principal = new User((String) map.get("username"), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } /** * 生成jwt * * @param user * @author LCheng * @date 2020/11/26 17:15 */ public String generate(User user) { String token = Jwts.builder() .claim(USER_KEY, user) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getValidateSecond() * 1000)) .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()).compact(); return token; } /** * 校验token * * @param token * @return {@link java.lang.Boolean} * @author LCheng * @date 2020/11/26 17:16 */ public Claims validate(String token) { try { return Jwts.parser().setSigningKey(jwtProperties.getSecretKey()).parseClaimsJws(token).getBody(); } catch (Exception e) { e.printStackTrace(); return null; } } }
此过滤器用来拦截请求,并解析token,并将解析的用户信息保存到spring security作用域中
/** * @author : LCheng * @date : 2020-11-21 14:36 * description : token验证的过滤器 */ @Slf4j @Component public class SecurityAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String token = jwtUtil.getToken(httpServletRequest); //判断token是否有效 if (StringUtils.hasText(token)) { //创建AuthenticationToken UsernamePasswordAuthenticationToken authentication = jwtUtil.getAuthentication(token); if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); } } else { log.debug("token无效。"); } filterChain.doFilter(httpServletRequest, httpServletResponse); } }
/** * @author : LCheng * @date : 2020-11-26 17:30 * description : bean */ @Component public class BeanConfig { @Bean public FilterRegistrationBean registration(SecurityAuthenticationTokenFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } /** * 使用BCrypt进行加密 * * @return {@link PasswordEncoder} * @author LCheng * @date 2020/11/26 17:57 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
这里是重中之重,所有登录验证功能都是在这里完成的
/** * 用户登录操作 * * @author lCheng */ @Component @Slf4j public class SecurityUserDetailService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String accountName) throws UsernameNotFoundException { log.info("登陆用户名:" + accountName); //这里进行用户验证 根据系统自行填写逻辑代码 //因为是测试,这里所有用户密码默认设置为123 return new User(accountName, passwordEncoder.encode("123"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); } }
这里是重中之重中之重,没有他什么也跑不起来,各块代码的注释都已表明
/** * @author : LCheng * @date : 2020-11-21 14:43 * description : spring security配置 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SecurityAccessDeniedHandler securityAccessDeniedHandler; @Autowired private SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SecurityAuthenticationTokenFilter securityAuthenticationTokenFilter; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); auth // 设置UserDetailsService .userDetailsService(userDetailsService) // 使用BCrypt进行加密 .passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config = httpSecurity // 禁用CSRF .csrf().disable() .exceptionHandling() //认证失败处理 .authenticationEntryPoint(securityAuthenticationEntryPoint) //无权限处理 .accessDeniedHandler(securityAccessDeniedHandler) // 不创建session 使用token不需要session .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 登录url不验证 .antMatchers("/login").permitAll() // OPTIONS请求不验证 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll(); // 剩下所有请求都需要认证 config.anyRequest().authenticated(); // 禁用缓存 httpSecurity.headers().cacheControl(); // 添加JWT filter httpSecurity .addFilterBefore(securityAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
/** * @author : LCheng * @date : 2020-11-26 17:44 * description : 测试控制器 */ @Slf4j @RestController public class TestController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtil jwtUtil; @PostMapping(value = "/login") public String login(@RequestParam String username, @RequestParam String password) { //验证用户信息 Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); SecurityContextHolder.getContext().setAuthentication(authentication); //生成token User user = (User) authentication.getPrincipal(); return jwtUtil.generate(user); } @GetMapping(value = "/hello") public String hello() { return "hello word"; } }
这里需要注意一下,SecurityConfig中有把 /login 放行,所以要与控制器中进行对应。
首先这里要安利一个工具,谷歌浏览器的一个插件: Talend API Tester ,本人喜欢用这个而不是postman。
接下来真是进入测试环节:
至此所有整合过程已经完毕。
源码已上传至gitee 地址为:https://gitee.com/lonecheng/springboot-springsecurity-jwt
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。