{"id":1950,"date":"2025-02-18T23:37:17","date_gmt":"2025-02-18T15:37:17","guid":{"rendered":"https:\/\/www.fanyamin.com\/wordpress\/?p=1950"},"modified":"2025-02-18T23:37:53","modified_gmt":"2025-02-18T15:37:53","slug":"spring-security-%e5%9b%9e%e9%a1%be","status":"publish","type":"post","link":"https:\/\/www.fanyamin.com\/wordpress\/?p=1950","title":{"rendered":"Spring Security \u56de\u987e\u4e00"},"content":{"rendered":"<h2>Spring Security \u6846\u67b6\u4ecb\u7ecd<\/h2>\n<h3>1. <strong>Spring Security \u662f\u4ec0\u4e48\uff1f<\/strong><\/h3>\n<p>Spring Security \u662f <strong>Spring \u6846\u67b6\u7684\u5b89\u5168\u7ba1\u7406\u6846\u67b6<\/strong>\uff0c\u7528\u4e8e\u63d0\u4f9b <strong>\u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09<\/strong> \u548c <strong>\u6388\u6743\uff08Authorization\uff09<\/strong> \u673a\u5236\u3002\u5b83\u4e3b\u8981\u7528\u4e8e\u4fdd\u62a4 <strong>Web \u5e94\u7528<\/strong> \u548c <strong>REST API<\/strong>\uff0c\u9632\u6b62 <strong>\u672a\u6388\u6743\u8bbf\u95ee<\/strong> \u548c <strong>\u5e38\u89c1\u5b89\u5168\u653b\u51fb<\/strong>\uff08\u5982 CSRF\u3001XSS\u3001Session Fixation\u3001Clickjacking \u7b49\uff09\u3002<\/p>\n<hr \/>\n<h3>2. <strong>Spring Security \u7684\u6838\u5fc3\u6982\u5ff5<\/strong><\/h3>\n<p>Spring Security \u4e3b\u8981\u6d89\u53ca\u4ee5\u4e0b\u6838\u5fc3\u6982\u5ff5\uff1a<\/p>\n<h4>2.1 <strong>\u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09<\/strong><\/h4>\n<p>\u8eab\u4efd\u8ba4\u8bc1\u662f\u6307 <strong>\u9a8c\u8bc1\u7528\u6237\u8eab\u4efd<\/strong> \u7684\u8fc7\u7a0b\u3002Spring Security \u652f\u6301\u591a\u79cd\u8ba4\u8bc1\u65b9\u5f0f\uff1a<\/p>\n<ul>\n<li><strong>\u7528\u6237\u540d + \u5bc6\u7801<\/strong>\uff08\u9ed8\u8ba4\u57fa\u4e8e <code>UserDetailsService<\/code>\uff09<\/li>\n<li><strong>JWT \u4ee4\u724c<\/strong>\uff08\u9002\u7528\u4e8e REST API\uff09<\/li>\n<li><strong>OAuth 2.0 \/ OpenID Connect<\/strong>\uff08\u652f\u6301 Google\u3001GitHub \u7b49\u7b2c\u4e09\u65b9\u767b\u5f55\uff09<\/li>\n<li><strong>LDAP<\/strong>\uff08\u4f01\u4e1a\u7ea7\u8eab\u4efd\u8ba4\u8bc1\uff09<\/li>\n<li><strong>SSO\uff08\u5355\u70b9\u767b\u5f55\uff09<\/strong><\/li>\n<li><strong>\u81ea\u5b9a\u4e49\u8ba4\u8bc1\u63d0\u4f9b\u8005\uff08Custom AuthenticationProvider\uff09<\/strong><\/li>\n<\/ul>\n<h4>2.2 <strong>\u6388\u6743\uff08Authorization\uff09<\/strong><\/h4>\n<p>\u6388\u6743\u662f\u6307 <strong>\u786e\u5b9a\u7528\u6237\u662f\u5426\u6709\u6743\u9650\u8bbf\u95ee\u8d44\u6e90<\/strong> \u7684\u8fc7\u7a0b\u3002Spring Security \u4e3b\u8981\u6709\u4e24\u79cd\u6388\u6743\u65b9\u5f0f\uff1a<\/p>\n<ul>\n<li><strong>\u57fa\u4e8e\u89d2\u8272\uff08Role-Based Access Control, RBAC\uff09<\/strong>\uff1a\n<ul>\n<li>\u4f8b\u5982\uff1a<code>hasRole(&#039;ADMIN&#039;)<\/code><\/li>\n<\/ul>\n<\/li>\n<li><strong>\u57fa\u4e8e\u6743\u9650\uff08Permission-Based Access Control, PBAC\uff09<\/strong>\uff1a\n<ul>\n<li>\u4f8b\u5982\uff1a<code>hasAuthority(&#039;READ_PRIVILEGE&#039;)<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>\u6388\u6743\u65b9\u5f0f\uff1a<\/p>\n<ul>\n<li><strong>\u65b9\u6cd5\u7ea7\u522b\u6388\u6743<\/strong>\uff08\u57fa\u4e8e <code>@PreAuthorize<\/code>, <code>@Secured<\/code>\uff09<\/li>\n<li><strong>URL \u7ea7\u522b\u6388\u6743<\/strong>\uff08\u57fa\u4e8e <code>HttpSecurity<\/code>\uff09<\/li>\n<li><strong>\u81ea\u5b9a\u4e49\u6388\u6743\u903b\u8f91<\/strong><\/li>\n<\/ul>\n<h4>2.3 <strong>\u5b89\u5168\u8fc7\u6ee4\u5668\uff08Security Filters\uff09<\/strong><\/h4>\n<p>Spring Security \u91c7\u7528 <strong>\u8fc7\u6ee4\u5668\u94fe\uff08Filter Chain\uff09<\/strong> \u8fdb\u884c\u8bf7\u6c42\u62e6\u622a\uff0c\u6bcf\u4e2a\u8bf7\u6c42\u90fd\u4f1a\u7ecf\u8fc7 <strong>\u591a\u4e2a\u5b89\u5168\u8fc7\u6ee4\u5668<\/strong>\uff1a<\/p>\n<ul>\n<li><strong>UsernamePasswordAuthenticationFilter<\/strong>\uff08\u5904\u7406\u8868\u5355\u767b\u5f55\uff09<\/li>\n<li><strong>BasicAuthenticationFilter<\/strong>\uff08\u5904\u7406 HTTP Basic \u8ba4\u8bc1\uff09<\/li>\n<li><strong>JwtAuthenticationFilter<\/strong>\uff08\u81ea\u5b9a\u4e49 JWT \u8ba4\u8bc1\uff09<\/li>\n<li><strong>SecurityContextPersistenceFilter<\/strong>\uff08\u7ba1\u7406\u7528\u6237 Session\uff09<\/li>\n<li><strong>ExceptionTranslationFilter<\/strong>\uff08\u5f02\u5e38\u5904\u7406\uff09<\/li>\n<\/ul>\n<h4>2.4 <strong>Spring Security \u7684\u5b89\u5168\u7279\u6027<\/strong><\/h4>\n<ul>\n<li><strong>CSRF \u4fdd\u62a4\uff08Cross-Site Request Forgery\uff09<\/strong><\/li>\n<li><strong>Session \u7ba1\u7406\uff08\u9632\u6b62\u4f1a\u8bdd\u56fa\u5b9a\u653b\u51fb\uff09<\/strong><\/li>\n<li><strong>\u5bc6\u7801\u52a0\u5bc6\uff08BCryptPasswordEncoder\uff09<\/strong><\/li>\n<li><strong>\u8de8\u57df\u8d44\u6e90\u5171\u4eab\uff08CORS\uff09\u652f\u6301<\/strong><\/li>\n<li><strong>\u4e24\u6b65\u9a8c\u8bc1\uff082FA\uff09<\/strong><\/li>\n<li><strong>\u5b89\u5168\u5934\u90e8\u7ba1\u7406\uff08Security Headers\uff09<\/strong><\/li>\n<\/ul>\n<hr \/>\n<h3>3. <strong>Spring Security \u7684\u57fa\u672c\u4f7f\u7528<\/strong><\/h3>\n<h4>3.1 <strong>\u6dfb\u52a0 Spring Security \u4f9d\u8d56<\/strong><\/h4>\n<p>\u5982\u679c\u4f7f\u7528 <strong>Spring Boot<\/strong>\uff0c\u53ef\u4ee5\u76f4\u63a5\u6dfb\u52a0 <code>spring-boot-starter-security<\/code>\uff1a<\/p>\n<pre><code class=\"language-xml\">&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n    &lt;artifactId&gt;spring-boot-starter-security&lt;\/artifactId&gt;\n&lt;\/dependency&gt;<\/code><\/pre>\n<h4>3.2 <strong>\u914d\u7f6e\u7b80\u5355\u7684\u7528\u6237\u540d\u5bc6\u7801\u767b\u5f55<\/strong><\/h4>\n<p>Spring Security <strong>\u9ed8\u8ba4\u63d0\u4f9b\u4e00\u4e2a <code>UserDetailsService<\/code>\uff0c\u9ed8\u8ba4\u7528\u6237\u540d\u662f <code>user<\/code>\uff0c\u5bc6\u7801\u5728\u63a7\u5236\u53f0\u751f\u6210<\/strong>\u3002\u6211\u4eec\u53ef\u4ee5\u81ea\u5b9a\u4e49\u7528\u6237\u540d\u5bc6\u7801\uff1a<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n    @Bean\n    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -&gt; auth\n                .requestMatchers(&quot;\/admin\/**&quot;).hasRole(&quot;ADMIN&quot;) \/\/ \u53ea\u6709 ADMIN \u89d2\u8272\u53ef\u4ee5\u8bbf\u95ee\n                .requestMatchers(&quot;\/user\/**&quot;).hasRole(&quot;USER&quot;) \/\/ \u53ea\u6709 USER \u89d2\u8272\u53ef\u4ee5\u8bbf\u95ee\n                .anyRequest().authenticated() \/\/ \u5176\u4ed6\u8bf7\u6c42\u5fc5\u987b\u767b\u5f55\n            )\n            .formLogin(withDefaults()) \/\/ \u542f\u7528\u9ed8\u8ba4\u7684\u767b\u5f55\u8868\u5355\n            .httpBasic(withDefaults()); \/\/ \u542f\u7528 HTTP Basic \u8ba4\u8bc1\n        return http.build();\n    }\n\n    @Bean\n    public UserDetailsService userDetailsService() {\n        UserDetails user = User.withDefaultPasswordEncoder()\n                .username(&quot;admin&quot;)\n                .password(&quot;admin123&quot;)\n                .roles(&quot;ADMIN&quot;)\n                .build();\n        return new InMemoryUserDetailsManager(user);\n    }\n}<\/code><\/pre>\n<hr \/>\n<h3>4. <strong>Spring Security + JWT \u5b9e\u73b0\u65e0\u72b6\u6001\u8ba4\u8bc1<\/strong><\/h3>\n<p>\u5bf9\u4e8e REST API\uff0c\u6211\u4eec\u901a\u5e38\u4f7f\u7528 <strong>JWT\uff08JSON Web Token\uff09<\/strong> \u8fdb\u884c\u65e0\u72b6\u6001\u8ba4\u8bc1\uff0c\u800c\u4e0d\u662f Session \u673a\u5236\u3002<\/p>\n<h4>4.1 <strong>\u6dfb\u52a0 JWT \u4f9d\u8d56<\/strong><\/h4>\n<pre><code class=\"language-xml\">&lt;dependency&gt;\n    &lt;groupId&gt;io.jsonwebtoken&lt;\/groupId&gt;\n    &lt;artifactId&gt;jjwt&lt;\/artifactId&gt;\n    &lt;version&gt;0.11.5&lt;\/version&gt;\n&lt;\/dependency&gt;<\/code><\/pre>\n<h4>4.2 <strong>\u5b9e\u73b0 JWT \u751f\u6210\u548c\u89e3\u6790<\/strong><\/h4>\n<pre><code class=\"language-java\">@Component\npublic class JwtUtil {\n    private final String SECRET_KEY = &quot;my_secret_key&quot;;\n\n    public String generateToken(String username) {\n        return Jwts.builder()\n                .setSubject(username)\n                .setIssuedAt(new Date())\n                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) \/\/ 1\u5c0f\u65f6\n                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)\n                .compact();\n    }\n\n    public String extractUsername(String token) {\n        return Jwts.parser()\n                .setSigningKey(SECRET_KEY)\n                .parseClaimsJws(token)\n                .getBody()\n                .getSubject();\n    }\n\n    public boolean validateToken(String token, UserDetails userDetails) {\n        return userDetails.getUsername().equals(extractUsername(token));\n    }\n}<\/code><\/pre>\n<h4>4.3 <strong>JWT \u8fc7\u6ee4\u5668<\/strong><\/h4>\n<pre><code class=\"language-java\">@Component\npublic class JwtAuthenticationFilter extends OncePerRequestFilter {\n    @Autowired\n    private JwtUtil jwtUtil;\n\n    @Autowired\n    private UserDetailsService userDetailsService;\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        String token = request.getHeader(&quot;Authorization&quot;);\n        if (token != null &amp;&amp; token.startsWith(&quot;Bearer &quot;)) {\n            token = token.substring(7);\n            String username = jwtUtil.extractUsername(token);\n            if (username != null &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {\n                UserDetails userDetails = userDetailsService.loadUserByUsername(username);\n                if (jwtUtil.validateToken(token, userDetails)) {\n                    UsernamePasswordAuthenticationToken authToken =\n                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());\n                    SecurityContextHolder.getContext().setAuthentication(authToken);\n                }\n            }\n        }\n        chain.doFilter(request, response);\n    }\n}<\/code><\/pre>\n<h4>4.4 <strong>\u5728 SecurityConfig \u4e2d\u6ce8\u518c JWT \u8fc7\u6ee4\u5668<\/strong><\/h4>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n    @Autowired\n    private JwtAuthenticationFilter jwtFilter;\n\n    @Bean\n    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\n        http\n            .csrf(AbstractHttpConfigurer::disable) \/\/ \u5173\u95ed CSRF\uff08\u56e0\u4e3a\u662f\u65e0\u72b6\u6001\u7684\uff09\n            .authorizeHttpRequests(auth -&gt; auth\n                .requestMatchers(&quot;\/api\/auth\/**&quot;).permitAll()\n                .anyRequest().authenticated()\n            )\n            .sessionManagement(session -&gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); \/\/ \u52a0\u5165 JWT \u8fc7\u6ee4\u5668\n        return http.build();\n    }\n}<\/code><\/pre>\n<hr \/>\n<h3>5. <strong>\u603b\u7ed3<\/strong><\/h3>\n<p>Spring Security \u662f <strong>Spring \u751f\u6001\u7cfb\u7edf\u4e2d\u6700\u5f3a\u5927\u7684\u5b89\u5168\u6846\u67b6<\/strong>\uff0c\u5b83\u63d0\u4f9b\u4e86\uff1a<br \/>\n\u2705 <strong>\u5f3a\u5927\u7684\u8eab\u4efd\u8ba4\u8bc1\u548c\u6388\u6743\u673a\u5236<\/strong><br \/>\n\u2705 <strong>\u9ed8\u8ba4\u5b89\u5168\u7b56\u7565\uff0c\u9632\u6b62\u5e38\u89c1\u653b\u51fb<\/strong><br \/>\n\u2705 <strong>\u652f\u6301\u591a\u79cd\u8ba4\u8bc1\u65b9\u5f0f\uff08JWT\u3001OAuth2\u3001LDAP\u3001SSO\uff09<\/strong><br \/>\n\u2705 <strong>\u53ef\u6269\u5c55\u7684\u5b89\u5168\u8fc7\u6ee4\u5668\u94fe<\/strong><\/p>\n<p>\u5bf9\u4e8e <strong>Web \u5e94\u7528<\/strong>\uff0c\u53ef\u4ee5\u4f7f\u7528 <strong>\u9ed8\u8ba4\u8868\u5355\u767b\u5f55<\/strong> \u6216 <strong>OAuth2 \u767b\u5f55<\/strong>\uff1b<br \/>\n\u5bf9\u4e8e <strong>REST API<\/strong>\uff0c\u63a8\u8350\u4f7f\u7528 <strong>JWT \u8fdb\u884c\u65e0\u72b6\u6001\u8ba4\u8bc1<\/strong>\u3002<\/p>\n<p>\u5982\u679c\u4f60\u6b63\u5728\u5f00\u53d1 <strong>Spring Boot \u5e94\u7528<\/strong>\uff0cSpring Security \u662f <strong>\u6700\u63a8\u8350\u7684\u5b89\u5168\u6846\u67b6<\/strong>\uff01\ud83d\ude80<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring Security \u6846\u67b6\u4ecb\u7ecd 1. Spring Security \u662f\u4ec0\u4e48\uff1f Spring Security \u662f Spring \u6846\u67b6\u7684\u5b89\u5168\u7ba1\u7406\u6846\u67b6\uff0c\u7528\u4e8e\u63d0\u4f9b \u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09 \u548c \u6388\u6743\uff08Authorization\uff09 \u673a\u5236\u3002\u5b83\u4e3b\u8981\u7528\u4e8e\u4fdd\u62a4 Web \u5e94\u7528 \u548c REST API\uff0c\u9632\u6b62 \u672a\u6388\u6743\u8bbf\u95ee \u548c \u5e38\u89c1\u5b89\u5168\u653b\u51fb\uff08\u5982 CSRF\u3001XSS\u3001Session Fixation\u3001Clickjacking \u7b49\uff09\u3002 2. Spring Security \u7684\u6838\u5fc3\u6982\u5ff5 Spring Security \u4e3b\u8981\u6d89\u53ca\u4ee5\u4e0b\u6838\u5fc3\u6982\u5ff5\uff1a 2.1 \u8eab\u4efd\u8ba4\u8bc1\uff08Authentication\uff09 \u8eab\u4efd\u8ba4\u8bc1\u662f\u6307 \u9a8c\u8bc1\u7528\u6237\u8eab\u4efd \u7684\u8fc7\u7a0b\u3002Spring Security \u652f\u6301\u591a\u79cd\u8ba4\u8bc1\u65b9\u5f0f\uff1a \u7528\u6237\u540d + \u5bc6\u7801\uff08\u9ed8\u8ba4\u57fa\u4e8e UserDetailsService\uff09 JWT \u4ee4\u724c\uff08\u9002\u7528\u4e8e REST API\uff09 OAuth 2.0 \/ OpenID Connect\uff08\u652f\u6301 Google\u3001GitHub [&hellip;] <a class=\"read-more\" href=\"https:\/\/www.fanyamin.com\/wordpress\/?p=1950\" title=\"Permanent Link to: Spring Security \u56de\u987e\u4e00\">&rarr;Read&nbsp;more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1950","post","type-post","status-publish","format-standard","hentry","category-5"],"_links":{"self":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1950"}],"collection":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1950"}],"version-history":[{"count":2,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1950\/revisions"}],"predecessor-version":[{"id":1952,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1950\/revisions\/1952"}],"wp:attachment":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1950"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1950"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1950"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}