springboot 版本
security 版本
wagger 版本
jwt 版本
redis 版本 pom文件如下
引入redis 是为了存储 token
<version>3.3.5</version>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.1.8</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 如果jdk大于1.8,则还需导入下面依赖-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--jwt-->
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
yaml文件的配置如下
server:
port: 8586 #端口号
servlet:
context-path: /api #接口统一自带前缀 (也就是因为这个配置很多地方需要改动)
spring:
application:
name: demoServe # 名字
datasource: #数据库
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:xx/xx?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useUnicode=true
username: xxx #数据库用户
password: xxxx #密码
data:
redis:
database: x #换成自己要用的库 例 1
host: xx.xx.xx.xx #换成自己的地址
port: xx #自己的端口
password:
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: 8
timeout: 5000
springdoc:
api-docs:
path: /v3/api-docs
group: default
enabled: true
swagger-ui: #去掉 url 和 config-url 参数 可以正常访问 加上后需要配置正确路径 (暂时不理解)不然报404错误。
path: /swagger-ui.html
#base-url: /api
enabled: true
创建 swagger 的配置文件 SwaggerConfig
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("demo接口文档")
.description("SpringBoot3 集成 Swagger3接口文档")
.version("v1"))
.externalDocs(new ExternalDocumentation()
.description("项目API文档")
.url("/"));
}
创建 security 的配置文件 如下
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class NewWebSecurityConfig {
@Autowired
UserNameAuthenticationProvider userNameAuthenticationProvider; //是用security代理的登录
private final JwtAuthenticationEntryPoint unauthorizedHandler; //未授权处理逻辑
private final RestAuthenticationAccessDeniedHandler accessDeniedHandler; //权限不足
private final JwtAuthenticationTokenFilter authenticationTokenFilter; //全局访问统一入口
public NewWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, RestAuthenticationAccessDeniedHandler accessDeniedHandler, JwtAuthenticationTokenFilter authenticationTokenFilter) {
this.unauthorizedHandler = unauthorizedHandler;
this.accessDeniedHandler = accessDeniedHandler;
this.authenticationTokenFilter = authenticationTokenFilter;
}
// 获取AuthenticationManager(认证管理器),登录时认证使用
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/","login/token","/api/login.token","/login/token").permitAll() // 放行的接口
.requestMatchers("/api/swagger-ui/**", "/api/*/api-docs/**").permitAll() // 允许未登录用户访问 Swagger UI .anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(AbstractHttpConfigurer::disable) // 禁用
.cors(cors -> cors.disable()) // 使用具体的实现对象 (这里我一直配不好 所以选择了禁用)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(accessDeniedHandler)
)
.authenticationProvider(userNameAuthenticationProvider);
// 登录前验证token
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers(
"/webjars/**",
"/api/swagger-ui.html/**",
"/swagger-resources/**",
"/api/swagger-resources/**",
"/v2/**",
"/v3/**",
"/swagger-ui/**",
"/swagger/**"
);
}
/**
* 配置 CORS
* @return // 因为上边配置一直 有问题 所以这里也注掉了
*/
/* @Bean
public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("/**")); // 允许所有来源
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的方法
configuration.setAllowedHeaders(List.of("*")); // 允许的头
configuration.setAllowCredentials(true); // 允许凭证
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }*/ /**
* 装载BCrypt密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5Utils.getMD5((String) rawPassword));
}
@Override
public String encode(CharSequence rawPassword) {
return MD5Utils.getMD5((String) rawPassword);
}
};
}
}
创建未授权处理逻辑文件 JwtAuthenticationEntryPoint
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
//验证为未登陆状态会进入此方法,认证错误
//System.out.println("认证失败:" + authException.getMessage());
response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
/* JsonResultInfo<String> result=new JsonResultInfo<String>();
result.setCode("401"); result.setMsg(authException.getMessage());*/ ObjectMapper mapper =new ObjectMapper();
Map<String,Object> map =new HashMap<String, Object>();
map.put("code", "401");
map.put("msg", "用户没有登录,请登录");
//String body = ResultJson.failure(ResultCode.UNAUTHORIZED, authException.getMessage()).toString();
printWriter.write(mapper.writeValueAsString(map));
// printWriter.write(Gson.toJSONString(result));
printWriter.flush();
}
}
创建权限不足文件 RestAuthenticationAccessDeniedHandler
@Component("restAuthenticationAccessDeniedHandler")
public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 登陆状态下,权限不足执行该方法
// System.out.println("权限不足:" + e.getMessage());
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 使用标准的403状态码
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter printWriter = response.getWriter();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("code", "403");
map.put("msg", "没有操作权限");
printWriter.write(mapper.writeValueAsString(map));
printWriter.flush();
}
}
创建全局访问统一入口文件JwtAuthenticationTokenFilter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private RedisTemplate<String, String> redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
System.out.println(request.getRequestURL());
if(JwtUtils.vaild(token, Constants.LOGIN_SERCET) && SecurityContextHolder.getContext().getAuthentication() == null) {
Claims claims = JwtUtils.getClaims(token, Constants.LOGIN_SERCET);
String userId = (String)claims.get("userId");
//去redis 拿到token 进行对比
String token1 = (String) redisTemplate.opsForHash().get(Constants.TOKEN + ":" + userId+":", Constants.KEY); //这里的常量可以自定义 与后边的文件对应上即可
String userStr = (String) redisTemplate.opsForHash().get(Constants.TOKEN + ":" + userId+":", Constants.USER );
if(token.equals(token1) && null !=userStr) {
ObjectMapper mapper =new ObjectMapper();
UserDetail userDetail = mapper.readValue(userStr, UserDetail.class);
redisTemplate.expire(Constants.TOKEN+":"+userId+":", 30, TimeUnit.MINUTES);
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info(String.format("Authenticated userDetail %s, setting security context", userDetail.getUsername()));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
创建UserNameAuthenticationProvider类
@Slf4j
@Component
public class UserNameAuthenticationProvider implements AuthenticationProvider {
@Autowired
private LoginServiceImpl loginService;
@SneakyThrows
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {
long time = System.currentTimeMillis();
log.info("用户名/密码 开始登录验证 time:{}", time);
LoginDto params = (LoginDto) authentication.getPrincipal();
UserDetail userDetails =loginService.loadUserByUsername(params);
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
result.setDetails(authentication.getDetails());
log.info("用户名/密码 登录验证完成 time:{}, existTime:{}", time, (System.currentTimeMillis() - time));
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
创建 controller
@RestController
@RequestMapping("/login")
@Tag(name = "登录模块",description = "login")
public class LoginController {
@Autowired
private AuthenticationManager authenticationManager;
@Resource
private RedisTemplate<String,String> redisTemplate;
@Operation(summary = "登录")
@PostMapping("/token")
public JsonResultInfo postToken(@RequestBody LoginDto param) throws JsonProcessingException {
Authentication authentication = null;
UserDetail userDetail = null;
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(param, param.getPassWord()));
SecurityContextHolder.getContext().setAuthentication(authentication);
userDetail = (UserDetail) authentication.getPrincipal();
JsonResultInfo jsonResultInfo = new JsonResultInfo();
if (userDetail != null) { // 用户不为空生成token 并存储
Claims cliams = Jwts.claims();
cliams.put("userId", userDetail.getId());
cliams.put("time", System.currentTimeMillis());
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<String, Object>();
String token = JwtUtils.createToken(cliams, Constants.LOGIN_SERCET);
map.put("token", token);
map.put("userId", userDetail.getId());
// map.put("clientCode", client.getClientCode());
// redisTemplate.opsForValue().set(Constants.TOKEN+":"+userDetail.getId()+":"+type, mapper.writeValueAsString(userDetail), 1000, TimeUnit.MINUTES); redisTemplate.opsForHash().put(Constants.TOKEN + ":" + userDetail.getId() + ":" , Constants.USER, mapper.writeValueAsString(userDetail));
redisTemplate.opsForHash().put(Constants.TOKEN + ":" + userDetail.getId() + ":" , Constants.KEY, token);
redisTemplate.expire(Constants.TOKEN + ":" + userDetail.getId() + ":", 30, TimeUnit.MINUTES);
jsonResultInfo.setCode(CommonEnum.LOGIN_SUCCESS.getResultCode());
jsonResultInfo.setMsg(CommonEnum.LOGIN_SUCCESS.getResultMsg());
jsonResultInfo.setData(map);
return jsonResultInfo;
}
return jsonResultInfo;
}
}
整合完毕
访问http://localhost:yourPort/api/swagger-ui/index.html#/
访问 swagger
如果 swagger 配置错误会出现一些pet user 这些接口检索自己的 swagger 配置。