class="hljs-ln-code"> class="hljs-ln-line">public class CustomUserDetailsService implements UserDetailsService {
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> @Autowired
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> private UserRepository userRepository;
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> User user = userRepository.findByUsername(username);
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> if (user == null) {
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> throw new UsernameNotFoundException("User not found");
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> }
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line">
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> return new org.springframework.security.core.userdetails.User(
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> user.getUsername(),
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> user.getPassword(),
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> getAuthority(user)
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> );
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> }
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line">
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> private Collectionextends GrantedAuthority> getAuthority(User user) {
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> return AuthorityUtils.createAuthorityList(user.getRoles().toArray(new String[0]));
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line"> }
  • class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line">}
  • class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
    2.3.2 权限控制示例

    基于角色的访问控制示例可以这样实现:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Configuration
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@EnableWebSecurity
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> @Autowired
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> private CustomUserDetailsService userDetailsService;
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(HttpSecurity http) throws Exception {
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> http
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> .authorizeRequests()
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> .antMatchers("/admin/**").hasRole("ADMIN")
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> .anyRequest().authenticated()
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> .formLogin()
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> .loginPage("/login")
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> .permitAll()
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    19. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> .logout()
    20. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> .permitAll();
    21. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> }
    22. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line">
    23. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    24. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="24"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    25. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="25"> class="hljs-ln-code"> class="hljs-ln-line"> auth.userDetailsService(userDetailsService);
    26. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="26"> class="hljs-ln-code"> class="hljs-ln-line"> }
    27. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="27"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这个例子中,我们通过 HttpSecurity 配置了不同路径的权限需求,并通过 AuthenticationManagerBuilder 配置了我们的自定义用户认证服务。这样,我们便实现了一个基于角色的权限控制模型。

    3. Spring Security的过滤器链和配置定制

    3.1 过滤器链的工作原理

    3.1.1 过滤器链的结构和顺序

    Spring Security通过一系列的过滤器来处理HTTP请求,这些过滤器构成一个过滤器链(Filter Chain)。每一个过滤器在链中都扮演特定的角色,比如认证、请求处理、异常处理等。了解过滤器链的工作原理,对理解和定制Spring Security配置至关重要。

    每个过滤器都按照特定的顺序排列,通常在请求被分发到Servlet之前,通过 FilterChainProxy 来调用。在Spring Security 5.x版本中,过滤器链可能包括但不限于以下过滤器:

    每个过滤器都有其默认的执行顺序,这个顺序可以通过 WebSecurityConfigurerAdapter 进行调整。开发者也可以自定义过滤器,并通过在 doFilter 方法中调用 FilterChain.doFilter(request, response) 将其插入到适当的链位置。

    3.1.2 自定义过滤器实现

    在某些场景下,我们需要自定义过滤器来执行一些特定的安全逻辑。Spring Security允许我们添加自己的过滤器到过滤器链中。以下是如何实现和添加一个简单的自定义过滤器的示例代码:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Component
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">public class CustomFilter extends GenericFilterBean {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> throws IOException, ServletException {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> // 实现过滤逻辑
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> // 比如可以在这里验证请求头、记录日志等操作
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> // 最后必须调用chain.doFilter(request, response)以继续过滤器链的执行
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> chain.doFilter(request, response);
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> }
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    然后,需要配置一个 WebSecurityConfigurerAdapter ,将自定义的过滤器添加到过滤器链中:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Configuration
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@EnableWebSecurity
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public class SecurityConfig extends WebSecurityConfigurerAdapter {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> @Autowired
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> private CustomFilter customFilter;
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(HttpSecurity http) throws Exception {
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> }
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在上面的代码中, customFilter 被添加到了 UsernamePasswordAuthenticationFilter 之前,意味着它将在这个过滤器执行之前执行其逻辑。

    3.2 配置定制化

    3.2.1 XML和Java配置的对比

    Spring Security允许通过XML配置文件和Java配置类两种方式来进行安全设置。XML配置方式以其清晰、直观的优点被许多开发者所喜爱,而Java配置方式则更符合Spring的编程风格,提供了类型安全和更好的IDE支持。

    以下是通过Java配置类实现与XML配置文件等效的安全设置的示例:

    XML配置示例(security.xml):

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line"><http>
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line"> <intercept-url pattern="/**" access="ROLE_USER" />
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> <form-login login-page="/login" default-target-url="/home" />
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">http>
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    Java配置示例(SecurityConfig.java):

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Configuration
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@EnableWebSecurity
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public class SecurityConfig extends WebSecurityConfigurerAdapter {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(HttpSecurity http) throws Exception {
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> http
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> .authorizeRequests()
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> .antMatchers("/**").hasRole("USER")
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> .formLogin()
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> .loginPage("/login")
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> .defaultSuccessUrl("/home")
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> .logout()
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> .permitAll();
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> }
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    从上述例子可以看出,Java配置方式使用链式调用方法,具有更好的可读性和易于编辑的特点,尤其是对于熟悉Java开发的开发者来说。同时,Java配置类通常会是更佳的选择,因为它们可以与其他Spring框架组件无缝集成。

    3.2.2 Web安全配置的高级选项

    Spring Security提供了非常灵活的配置选项,允许我们进行细粒度的安全配置。除了最基础的请求匹配规则之外,我们还可以进行如下配置:

    以下是一个Web安全配置的高级选项实例:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@EnableWebSecurity
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">public class AdvancedWebSecurityConfig extends WebSecurityConfigurerAdapter {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(HttpSecurity http) throws Exception {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> http
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> .authorizeRequests()
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> .antMatchers("/admin/**").hasRole("ADMIN")
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> .antMatchers("/public/**").permitAll()
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> .anyRequest().authenticated()
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> .formLogin()
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> .loginPage("/login")
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> .usernameParameter("email")
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> .passwordParameter("password")
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> .loginProcessingUrl("/perform_login")
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> .defaultSuccessUrl("/home", true)
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> .failureUrl("/login?error=true")
    19. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> .permitAll()
    20. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    21. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> .logout()
    22. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line"> .logoutUrl("/perform_logout")
    23. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line"> .logoutSuccessUrl("/login?logout")
    24. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="24"> class="hljs-ln-code"> class="hljs-ln-line"> .deleteCookies("JSESSIONID")
    25. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="25"> class="hljs-ln-code"> class="hljs-ln-line"> .invalidateHttpSession(true)
    26. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="26"> class="hljs-ln-code"> class="hljs-ln-line"> .permitAll()
    27. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="27"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    28. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="28"> class="hljs-ln-code"> class="hljs-ln-line"> .rememberMe()
    29. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="29"> class="hljs-ln-code"> class="hljs-ln-line"> .tokenValiditySeconds(60*60*24*30)
    30. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="30"> class="hljs-ln-code"> class="hljs-ln-line"> .key("my-remember-me-key")
    31. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="31"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    32. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="32"> class="hljs-ln-code"> class="hljs-ln-line"> .csrf()
    33. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="33"> class="hljs-ln-code"> class="hljs-ln-line"> .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    34. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="34"> class="hljs-ln-code"> class="hljs-ln-line"> .ignoringAntMatchers("/api/**");
    35. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="35"> class="hljs-ln-code"> class="hljs-ln-line"> }
    36. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="36"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这个配置中,我们对登录页面、记住我功能、注销功能、CSRF保护等进行了定制。通过 HttpSecurity 对象提供的方法,可以灵活配置几乎所有的安全特性。

    3.3 高级配置实例分析

    3.3.1 多重认证机制配置

    在一些复杂的业务场景中,我们可能需要根据不同的请求类型或者用户角色,使用多重认证机制。例如,对于敏感操作需要二次认证。这可以通过自定义 AuthenticationManager AuthenticationProvider 来实现。

    下面是一个示例,展示如何配置基于表单的认证和基于Token的认证:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Configuration
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@EnableWebSecurity
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public class MultiAuthenticationConfig extends WebSecurityConfigurerAdapter {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> @Autowired
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> private UserDetailsService userDetailsService;
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> auth
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> .userDetailsService(userDetailsService)
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> .authenticationProvider(tokenAuthenticationProvider());
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> }
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line">
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> @Bean
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> public AuthenticationProvider tokenAuthenticationProvider() {
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> return new TokenAuthenticationProvider();
    19. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> }
    20. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> @Bean
    21. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> @Override
    22. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line"> public AuthenticationManager authenticationManagerBean() throws Exception {
    23. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line"> return super.authenticationManagerBean();
    24. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="24"> class="hljs-ln-code"> class="hljs-ln-line"> }
    25. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="25"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    3.3.2 定制化安全拦截器

    在Spring Security中, FilterSecurityInterceptor 负责在请求通过过滤器链之后进行安全检查。我们可以定制化这个拦截器来实现一些高级的安全策略。

    下面是一个简单的示例,说明如何通过 FilterSecurityInterceptor 来添加自定义的访问决策管理器:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Bean
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">public FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> securityInterceptor.setAuthenticationManager(authenticationManager());
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> securityInterceptor.setAccessDecisionManager(myAccessDecisionManager());
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> securityInterceptor.setSecurityMetadataSource(mySecurityMetadataSource());
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> return securityInterceptor;
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">}
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line">@Bean
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">public AccessDecisionManager myAccessDecisionManager() {
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> List> decisionVoters
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> = Arrays.asList(new RoleVoter(), new AuthenticatedVoter());
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> return new AffirmativeBased(decisionVoters);
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line">}
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line">
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line">@Bean
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line">public SecurityMetadataSource mySecurityMetadataSource() {
    19. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> // 自定义资源权限元数据源
    20. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> return new CustomSecurityMetadataSource();
    21. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在上述配置中,我们自定义了访问决策管理器 myAccessDecisionManager 和安全元数据源 mySecurityMetadataSource ,以便可以按照我们定义的规则来进行权限控制。

    4. Spring Security的高级特性

    4.1 表达式式访问控制

    4.1.1 Spring表达式语言基础

    Spring表达式语言(SpEL)是一个功能强大的表达式语言,支持在运行时查询和操作对象图。它为Java语言中的操作提供了表达式语言的语法,能够以声明的方式指定验证规则和其他的约束。

    SpEL定义了一个表达式解析器,它有一个非常简单的API。你可以通过解析器的 parseExpression 方法来解析字符串格式的表达式文本,然后使用 getValue 方法来获取表达式的值。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">ExpressionParser parser = new SpelExpressionParser();
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">Expression exp = parser.parseExpression("'Hello World'.concat('!')");
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">String message = (String) exp.getValue();
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在上面的例子中,我们创建了一个SpEL表达式,用于将字符串 "Hello World" "!" 连接起来。这个过程首先创建了一个表达式解析器,然后解析了一个字符串表达式,最后获取了该表达式的值。

    SpEL还支持设置变量和函数,可以在表达式中直接访问它们:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">EvaluationContext context = new StandardEvaluationContext();
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">context.setVariable("message", "Hello World");
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">ExpressionParser parser = new SpELExpressionParser();
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">Expression exp = parser.parseExpression("#message.concat('!')");
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">String message = (String) exp.getValue(context);
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这个例子中,我们设置了一个变量 message 并将其赋值为 "Hello World" ,然后在表达式中引用了这个变量。

    4.1.2 表达式在访问控制中的应用

    Spring Security利用SpEL表达式语言提供了一种灵活的访问控制机制。使用SpEL,开发者可以定义复杂的权限检查逻辑,这些逻辑在运行时被评估。这是通过 @PreAuthorize @PostAuthorize @Secured 等注解实现的,其中 @PreAuthorize @PostAuthorize 使用SpEL来控制访问。

    例如,你可以在控制器方法上应用 @PreAuthorize 注解来保护对某个HTTP请求的访问:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@PreAuthorize("hasRole('ROLE_ADMIN')")
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@RequestMapping("/admin")
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public String adminPanel() {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> return "adminPage";
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这个例子中,只有拥有 ROLE_ADMIN 角色的用户才能访问 adminPanel 方法。 hasRole 是SpEL中的一个常用函数,用于检查当前认证用户是否拥有指定的角色。

    4.2 角色与权限管理

    4.2.1 角色继承和权限分配

    在Spring Security中,角色和权限的概念是访问控制的核心。一个角色通常关联了一组权限,而权限则定义了用户可以执行的具体操作。

    角色继承是指一个角色可以继承另一个角色的权限,这样可以实现角色的层次结构。Spring Security通过 SimpleAuthorityMapper HierarchyAuthorityMapper 这样的类来实现角色继承功能。例如, ROLE_ADMIN 可能继承 ROLE_USER 的所有权限,并添加额外的管理权限。

    权限分配则是指将权限授予给特定的角色。这通常在配置文件中定义,或者在启动时通过代码配置:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Bean
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">public GrantedAuthority[] defaultAuthorities() {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> return AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN").toArray(new GrantedAuthority[0]);
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这里,我们创建了一个包含 ROLE_USER ROLE_ADMIN 的权限列表,这将分配给所有认证的用户。

    4.2.2 权限的细粒度控制

    细粒度控制意味着可以对访问控制进行非常具体和详细的定义。在Spring Security中,这通常涉及对方法级别的安全性和URL访问控制的管理。

    要实现细粒度的权限控制,可以使用 @Secured 注解来指定方法级别的权限要求:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Secured("ROLE_ADMIN")
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@RequestMapping("/user/delete")
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">public String deleteUser(@RequestParam("userId") Long userId) {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> // 删除用户逻辑
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> return "userDeleted";
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这个例子中,只有 ROLE_ADMIN 角色的用户才能调用 deleteUser 方法。这种方式可以精确控制每个方法的安全性,为不同的用户或角色提供不同级别的访问权限。

    4.3 CSRF防护策略

    4.3.1 CSRF攻击的原理

    跨站请求伪造(CSRF)是一种攻击者利用用户对网站的信任来欺骗用户执行非法操作的攻击方式。CSRF攻击依赖于用户的会话和网站对用户的信任。攻击者通过诱导用户点击恶意链接或者打开恶意页面来执行网站上的操作。

    CSRF攻击通常利用以下条件: - 用户已经登录网站,并拥有有效的会话。 - 用户执行的操作不需要额外的用户验证。 - 网站无法区分是由用户主动发起的操作还是由第三方发起的操作。

    4.3.2 CSRF防护配置与实施

    Spring Security提供了内置的CSRF保护机制,通过生成一个同步令牌(CSRF token)来防止CSRF攻击。这个令牌在用户登录时生成,并随表单一起发送到客户端。在后续的操作中,服务器会验证请求中携带的CSRF token是否有效。

    要启用CSRF保护,你需要在Spring Security配置中启用CSRF:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">@Override
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">protected void configure(HttpSecurity http) throws Exception {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> http
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> // ... 其他配置 ...
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> .and()
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> .csrf()
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在这里,我们配置了 HttpSecurity 来启用CSRF保护,并指定了使用 CookieCsrfTokenRepository 来管理CSRF token。 withHttpOnlyFalse() 方法确保生成的cookie可以被JavaScript访问,这在单页面应用(SPA)中非常有用。

    通过这种方式,每个HTTP POST请求都需要携带CSRF token才能被服务器接受。这样可以有效地防止CSRF攻击,因为第三方无法获取该token来模拟用户操作。

    请注意,CSRF保护默认对于GET、HEAD、OPTIONS和TRACE请求是禁用的,因为这些请求类型通常不会改变服务器的状态。而对于需要修改服务器状态的请求,如POST、PUT、DELETE等,Spring Security会强制要求CSRF token。

    5. Angular JS核心特性解析

    5.1 双向数据绑定原理

    Angular JS中的双向数据绑定是其核心特性之一,它允许视图和模型之间的数据自动同步,从而极大地简化了前端开发。为了理解其工作原理,我们需要深入探讨几个关键概念。

    5.1.1 数据绑定的实现机制

    在传统Web应用中,开发者需要手动编写事件监听器来同步视图和数据。Angular JS则利用脏值检查器(Digest Cycle)来自动处理数据和视图之间的同步。当应用的状态发生变化时,脏值检查器会遍历所有绑定的模型,检查它们是否有变化。如果有变化,视图会自动更新,反之则保持不变。

    代码示例:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">angular.module('app', [])
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">.controller('MainCtrl', function($scope) {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> $scope.message = 'Hello World!';
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在上述代码中,我们创建了一个简单的Angular JS应用,其中包含一个控制器 MainCtrl ,它有一个 $scope 对象,该对象包含了视图中将要显示的消息 message 。当 $scope.message 在控制器中被改变时,视图会自动更新以反映新的值。

    5.1.2 双向绑定的使用场景和优势

    双向数据绑定适用于创建动态的、响应式的用户界面。例如,在表单输入、实时搜索、动态列表等场景中,双向绑定可以极大地减少代码量,提高开发效率。其优势在于,开发者不需要编写额外的代码来保持视图和模型的同步,从而可以专注于业务逻辑的实现。

    示例表格展示:

    | 场景类型 | 优势描述 | |--------------|----------------------------------| | 表单输入 | 用户输入时,数据自动更新模型,反之亦然。 | | 实时搜索 | 搜索条件变化时,结果显示实时更新。 | | 动态列表 | 列表项的增删改,视图同步更新。 |

    5.2 指令的创建与应用

    Angular JS的指令是扩展HTML标签的自定义元素,它们可以用来创建可复用的组件,封装复杂的逻辑,提供更加动态和交互式的用户界面。

    5.2.1 指令的分类和功能

    指令可以大致分为以下几类:

    每个指令都可以有自己的行为,包括监听事件、操作DOM、与外部资源交互等。

    5.2.2 自定义指令的编写流程

    创建一个Angular JS指令需要几个步骤:

    1. 使用 .directive() 方法注册指令。
    2. 指定指令的名称和配置对象。
    3. 在配置对象中定义指令的行为。

    下面是一个简单的自定义指令示例:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">angular.module('app').directive('myDirective', function() {
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line"> return {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> restrict: 'E', // E = element, A = attribute, C = class, M = comment
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> template: '
      A custom directive!
      '
      ,
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> link: function(scope, element, attrs) {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> element.bind('click', function() {
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> element.text('You clicked me!');
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> });
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> }
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> };
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    在上面的例子中,我们创建了一个名为 myDirective 的元素指令。当用户点击元素时,它会改变显示的文本。

    5.3 服务与模块化设计

    模块化是软件设计中的一个重要概念,它允许开发者将大型应用分解为小的、可管理的组件。Angular JS通过服务(Service)和模块(Module)支持模块化设计。

    5.3.1 服务的创建和依赖注入

    在Angular JS中,服务是单例对象,用来封装应用的业务逻辑、数据和函数。通过依赖注入(Dependency Injection),服务可以被应用中的其他部分复用。

    创建服务的代码如下:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">angular.module('app').service('myService', function() {
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line"> this.greet = function(name) {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> return 'Hello, ' + name + '!';
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> };
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    依赖注入的示例:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">angular.module('app').controller('MainCtrl', function($scope, myService) {
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line"> $scope.greet = function(name) {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> return myService.greet(name);
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> };
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    5.3.2 模块化组织代码的最佳实践

    模块化组织代码通常遵循以下最佳实践:

    例如,一个典型的Angular JS应用模块可能看起来像这样:

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">var myApp = angular.module('myApp', []);
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">myApp.config(function($stateProvider, $urlRouterProvider) {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> // 路由配置
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">});
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">myApp.directive('myDirective', function() {
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> // 指令定义
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">});
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line">
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">myApp.service('myService', function() {
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> // 服务定义
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line">});
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line">
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line">myApp.controller('MainCtrl', function($scope, myService) {
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> // 控制器定义
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    通过这种组织方式,代码库更加清晰、易于维护,并且可以轻松地扩展新功能或组件。

    6. Angular JS进阶应用开发

    6.1 路由管理深入探讨

    6.1.1 路由的工作原理

    Angular JS中的路由是管理应用视图状态的一个核心组件。它允许开发者将不同的URL映射到不同的视图或组件,同时也能管理用户的导航历史记录。路由的工作原理涉及到几个核心概念,包括路由配置、路由解析、路由守卫和组件的激活与嵌套。

    在Angular中,路由配置是通过定义路由对象数组来实现的,每个对象都包含了路径信息、组件信息以及其它路由元数据。当用户触发导航时(如点击链接),Angular的路由器会根据当前的URL和路由配置来决定展示哪个组件。如果匹配成功,路由器将使用指定的组件来更新视图。如果路径信息中定义了参数,路由器会解析这些参数并将它们作为依赖注入到组件中。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Angular路由配置
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">const routes: Routes = [
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> { path: '', component: HomeComponent },
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> { path: 'about', component: AboutComponent },
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> { path: 'contact', component: ContactComponent },
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">];
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    路由解析是可选的步骤,允许在导航到一个组件之前先执行特定的逻辑。例如,我们可以从服务中获取数据并在数据加载完成后导航到目标组件。路由守卫则提供了一种机制来控制访问权限,防止未授权的用户访问某些路由。

    6.1.2 动态路由和路由守卫

    动态路由是指在路由路径中包含一个或多个参数,这些参数在导航过程中被动态解析并作为参数传递给对应组件。在Angular中,动态路由是通过路径中带有冒号(:)的参数来定义的。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Angular动态路由配置
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">const routes: Routes = [
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> { path: 'users/:id', component: UserComponent },
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">];
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    路由守卫则是一组接口,它们可以返回一个Observable、Promise或直接返回一个值来指示路由导航是否被允许。Angular提供了几个内置的路由守卫,如 CanActivate CanActivateChild CanDeactivate Resolve CanLoad 。它们可以在不同的导航阶段提供更细粒度的控制。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:使用CanActivate守卫
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@Injectable({
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> providedIn: 'root'
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">})
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">export class AuthGuard implements CanActivate {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> constructor(private authService: AuthService) {}
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> canActivate(
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> route: ActivatedRouteSnapshot,
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> state: RouterStateSnapshot
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> ): boolean {
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> return this.authService.isAuthenticated();
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> }
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    路由守卫使得开发者可以在用户进入路由之前或在离开路由之前执行特定的逻辑。例如, CanActivate 守卫用于检查用户是否已经认证,如果用户未认证则阻止其进入特定路由。路由守卫的这些特性在构建复杂的导航策略和应用安全中非常有用。

    6.2 API安全和无状态认证

    6.2.1 token机制的实现

    在现代的Web应用中,尤其是涉及前后端分离的架构中,token机制是实现无状态认证的一种流行方式。Angular JS虽然本身不直接涉及token的生成与验证,但通常会与后端服务配合来实现这一机制。Token通常是一个加密的字符串,包含了用户身份信息和其它可选的声明。

    Angular JS应用通过调用后端的认证接口获取token,之后将token存储在localStorage、sessionStorage或者作为HTTP请求的头部发送给后端服务。当用户发起每个API请求时,Angular应用都会在请求头中附带这个token。后端服务接收到请求后,会验证token的有效性,以确认用户的身份。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Angular中使用HttpInterceptor添加token到请求头
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@Injectable()
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">export class TokenInterceptor implements HttpInterceptor {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> const token = localStorage.getItem('token');
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> const authReq = req.clone({
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> headers: req.headers.set('Authorization', `Bearer ${token}`)
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> });
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> return next.handle(authReq);
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> }
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    Token机制的优势在于它可以实现无状态的认证,这意味着后端服务不需要在每次请求时都查询数据库来验证用户信息。Token认证还增强了安全性,因为它可以包含多个声明,如用户的角色和权限信息,这些都可以在应用中用于访问控制。

    6.2.2 Angular与后端的交互安全

    为了保证Angular与后端API的交互安全,需要使用HTTPS协议来加密传输数据,并对敏感数据进行签名或加密。Token机制中,虽然可以使用HTTPS来保证数据传输的安全,但还必须对Token本身进行处理,防止泄露和被篡改。

    在Angular应用中,可以使用拦截器(Interceptor)来自动处理每个请求的Token,无需手动在每个请求中添加Token。此外,对于敏感操作如修改密码或更改个人资料,可以使用额外的验证机制,例如一次性验证码(OTP)。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Angular中使用OTP进行敏感操作
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">// 这里只是一个概念性的代码示例,展示如何在Angular应用中处理OTP
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">if (operationIsSensitive) {
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> const otp = prompt('请输入您的验证码');
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> if (validateOtp(otp)) {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> // 如果OTP有效,则执行敏感操作
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> } else {
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> // 如果OTP无效,则阻止操作
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> }
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    当Angular应用接收到来自后端的响应时,也需要进行错误处理。例如,如果后端返回了401未授权状态码,Angular应该能够处理这个响应,清除token并重定向用户到登录页面。

    6.3 前后端授权策略一致性

    6.3.1 授权策略的设计原则

    设计前后端授权策略时,要确保策略的一致性和可扩展性。授权策略通常依赖于用户的角色和权限,这些信息应当在用户登录时由后端服务验证,并在成功认证后发送给客户端。前端应用需要根据这些信息来决定用户是否有权访问特定的路由或执行特定的操作。

    授权策略的设计应当遵循最小权限原则,即用户仅拥有执行其任务所必需的最小权限集。这样的设计可以减少安全漏洞的风险,同时也满足了业务需求。在设计时,还应考虑策略的模块化,使得在将来添加新的权限或角色时,能够轻松地集成新的授权逻辑。

    6.3.2 实现前后端授权一致性策略

    要在前后端实现一致的授权策略,必须确保策略在两端都得到严格执行。前端应用通常会在路由守卫中实现授权检查,而后端API则会在接口层进行角色和权限的校验。

    在Angular中,可以创建一个通用的 AuthorizedComponent ,它接收一个角色数组作为输入,然后通过 CanActivate 守卫来检查当前用户的角色是否匹配。只有匹配的角色才能访问该组件。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Angular授权组件守卫
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">@Injectable({
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> providedIn: 'root'
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">})
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">export class RoleGuard implements CanActivate {
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> constructor(private router: Router, private authService: AuthService) {}
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> canActivate(
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> route: ActivatedRouteSnapshot,
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> state: RouterStateSnapshot
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> ): boolean {
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> const requiredRoles = route.data['roles'];
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> if (requiredRoles) {
    14. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line"> const userRoles = this.authService.getUserRoles();
    15. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> const hasPermission = requiredRoles.some(role => userRoles.includes(role));
    16. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> if (!hasPermission) {
    17. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line"> this.router.navigate(['/forbidden']);
    18. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> return false;
    19. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> }
    20. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line"> }
    21. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> return true;
    22. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line"> }
    23. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hide-preCode-box"> class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    后端API则会在处理请求之前,检查用户的角色和权限,确保只有授权用户可以访问特定资源。这通常通过中间件来实现,如Node.js中使用Express框架的中间件。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 示例:Node.js后端的授权中间件
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">app.use(async (req, res, next) => {
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> const token = req.headers.authorization;
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> if (!token) {
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line"> return res.status(401).send('Token missing');
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> }
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> const user = await verifyToken(token);
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line"> const hasPermission = await checkPermission(user.role, req.path);
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> if (!hasPermission) {
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> return res.status(403).send('Forbidden');
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line"> }
    12. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> next();
    13. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line">});
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    通过在前后端同时实施一致的授权策略,可以确保应用的整体安全性,防止未授权的访问和操作。这要求前后端开发团队紧密合作,确保策略的一致性和兼容性。在设计过程中,还应该考虑到策略的可维护性和灵活性,使其能够适应快速变化的业务需求和技术环境。

    7. 安全应用的高级实践

    7.1 错误处理的定制化

    7.1.1 错误处理的常见问题

    在进行安全应用的开发时,错误处理是不可忽视的一部分。错误处理不当不仅会影响用户体验,还可能成为安全隐患。常见的错误处理问题包括:

    7.1.2 自定义错误处理流程

    为了有效地解决上述问题,我们需要对错误处理进行定制化设计。以下是一个自定义错误处理流程的示例:

    1. 定义错误模型 :首先定义一个统一的错误模型,包含错误类型、错误消息、状态码等字段。 java public class CustomErrorResponse { private String error; private String message; private int status; // getters and setters }

    2. 创建全局异常处理器 :使用 @ControllerAdvice 注解创建一个全局异常处理器,统一捕获和处理异常。

    java @ControllerAdvice public class CustomGlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { CustomErrorResponse response = new CustomErrorResponse(); response.setError("Server Error"); response.setMessage(e.getMessage()); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } // 其他特定异常的处理方法 }

    1. 日志记录 :对所有捕获的异常进行日志记录,记录异常信息和相关上下文信息,便于问题的追踪和定位。

    java log.error("Exception occurred: ", e);

    1. 安全友好的错误消息 :对外展示的错误信息应保持一致,并避免透露敏感信息。

    7.2 安全的登录流程设计

    7.2.1 安全登录机制的设计

    一个安全的登录机制需要考虑防止多种攻击手段,例如暴力破解、SQL注入、会话固定和跨站请求伪造(CSRF)。以下是一些设计要点:

    7.2.2 防止CSRF的登录实现

    对于防止CSRF攻击,我们可以在后端生成一个CSRF token,并将其存储在用户的会话中,同时将此token作为一个隐藏字段或cookie返回给客户端。在用户提交登录表单时,需要验证这个token。

    1. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">// 在登录页面渲染时,添加一个隐藏的CSRF token字段
    2. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">"hidden" name="_csrf" value="${_csrf.token}">
    3. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
    4. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">// 在登录请求中,携带CSRF token进行验证
    5. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">HttpSession session = request.getSession();
    6. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line">String storedToken = (String) session.getAttribute("CSRF_TOKEN");
    7. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line">CsrfToken tokenInRequest = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    8. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">String tokenInPost = request.getParameter(tokenInRequest.getParameterName());
    9. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">if (!storedToken.equals(tokenInPost)) {
    10. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> throw new InvalidCsrfTokenException("CSRF token mismatch");
    11. class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">}
    class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">

    7.3 实战项目中的安全策略

    7.3.1 安全策略的应用案例

    在实战项目中,安全策略的应用需要全面考虑认证、授权、数据安全、API安全等多方面因素。以下是一个应用案例:

    7.3.2 案例中的问题与解决方案

    在案例实施过程中,可能会遇到以下问题及其解决方案:

    通过上述策略和问题解决方案,可以极大地提升应用的安全性,保证系统的稳定运行。

    本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

    简介:本项目介绍如何将Spring Security和Angular JS结合起来,为Web应用程序构建一个安全框架。Spring Security提供全面的认证和授权管理,而Angular JS作为前端框架,支持构建动态交互式应用。整合两者的知识点包括认证与授权机制、过滤器链、配置、表达式式访问控制、角色与权限,以及Angular JS的双数据绑定、指令、服务、模块化和路由。通过实现API安全、CSRF防护、认证流程、授权策略和错误处理,开发者可以构建既安全又具有现代化用户体验的Web应用。

    本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/weixin_29069575/article/details/143463000","extend1":"pc","ab":"new"}">>
    注:本文转载自blog.csdn.net的疑样的文章"https://blog.csdn.net/weixin_29069575/article/details/143463000"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
    复制链接

    评论记录:

    未查询到任何数据!