记录springboot 3.3.5 版本整合 swagger +spring security + jwt

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 配置。

posted @ 2024-10-31 17:17  仅此忆念  阅读(961)  评论(0)    收藏  举报