記錄springboot 3.3.5 版本整合 swagger +spring security + jwt

仅此忆念發表於2024-10-31

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

相關文章