目录
  • 前言
  • 入门
    • 测试接口
    • 增加依赖
  • 自定义配置
    • 配置密码加密方式
    • 配置AuthenticationManagerBuilder 认证用户、角色权限
    • 配置HttpSecurity Url访问权限
    • 自定义successHandler
    • 自定义failureHandler
    • 自定义未认证处理
    • 自定义权限不足处理
    • 自定义注销登录
  • 前后端分离场景
    • 提供登录接口
    • 自定义认证过滤器
    • 鉴权
      • 1.注解鉴权
      • 2.自定义Bean动态鉴权
      • 3.扩展默认方法自定义扩展根对象SecurityExpressionRoot
    • 登出
      • 跨域
        • 全局配置

        前言

        Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链,不同的功能经由不同的过滤器。这篇文章给大家讲解SpringBoot Security从入门到实战,内容如下所示:

        入门

        测试接口

        假设我们用下面的接口做权限测试。

        @RestController
        public class LakerController {
            @GetMapping("/laker")
            public String laker() {
                return IdUtil.simpleUUID();
            }
            @GetMapping("/laker/q")
            public String lakerQ() {
                return IdUtil.simpleUUID();
            }
        }

        浏览器访问:http://localhost:8080/laker,结果如下:

        SpringBoot Security从入门到实战示例教程

        增加依赖

        pom.xml,添加 spring-boot-starter-securtiy 依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        

        再次访问http://localhost:8080/laker,结果如下:

        SpringBoot Security从入门到实战示例教程

        简要解析

        我们访问http://localhost:8080/laker,security判断我们没有登录,则会302重定向http://localhost:8080/login(默认)

        SpringBoot Security从入门到实战示例教程

        • security会返回一个默认的登录页。
        • 默认用户名为:user,密码在服务启动时,会随机生成一个,可以查看启动日志如下:

        2022-05-02 21:01:03.697  INFO 17896 — [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
        2022-05-02 21:01:03.825  INFO 17896 — [           main] .s.s.UserDetailsServiceAutoConfiguration : 

        Using generated security password: e53fef6a-3f61-43c3-9609-ce88fd7c0841

        当然,可以通过配置文件设置默认的用户名、密码、角色。

        spring:
          security:
            user:
              # 默认是 user
              name: laker
              password: laker
              roles:
                - ADMIN
                - TESTER

        以上的默认都是可以修改的哈。

        判断是否登录使用传统的cookie session模式哈,JSESSIONID E3512CD1A81DB7F2144C577BA38D2D92 HttpOnly true

        自定义配置

        实际项目中我们的用户、密码、角色、权限、资源都是存储在数据库中的,我们可以通过自定义类继承 WebSecurityConfigurerAdapter,从而实现对 Spring Security 更多的自定义配置。

        @Configuration
        public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
        		...
        }
        

        配置密码加密方式

        必须配置,否则报空指针异常。

           @Bean
            PasswordEncoder passwordEncoder() {
                // 不加密
                return NoOpPasswordEncoder.getInstance();
            }
        

        Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder

        BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strengthSecureRandom 实例。strength 取值在 4~31 之间(默认为 10)。strength 越大,密钥的迭代次数越多(密钥迭代次数为 2^strength)。如果是数据库认证,库里的密码同样也存放加密后的密码。同一密码每次 Bcrypt 生成的结果都会变化不会重复。

        配置AuthenticationManagerBuilder 认证用户、角色权限

        支持直接配置内存认证模式和配置UserDetailsServiceBean方式

        内存认证模式,实际项目不用这个哦。(仅做了解)

         /**
             * 配置用户及其对应的角色
             */
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.inMemoryAuthentication()
                        .withUser("admin").password("123").roles("ADMIN", "USER")
                        .and()
                        .withUser("laker").password("123").roles("USER");
            }
        

        上面在UserDetailsService的实现之一InMemoryUserDetailsManager中,即存储在内存中。

        自定义UserDetailsServiceBean方式,实际项目都是使用这个,可定制化程度高。

        步骤一:定义一个LakerUserService实现UserDetailsService,配置成SpringBean。该方法将在用户登录时自动调用。

        @Service
        public class LakerUserService implements UserDetailsService {
            @Autowired
            UserMapper userMapper;
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                // username 就是前端传递的例如 laker 123,即 laker
                User user = userMapper.loadUserByUsername(username);
                if (user == null) {
                    throw new UsernameNotFoundException("账户不存在!");
                }
                user.setAuthorities(...);
                return user;
            }
        }
        

        username

        password(加密后的密码)

        authorities

        返回的Bean中以上3个都不能为空。返回User后由系统提供的 DaoAuthenticationProvider 类去比对密码是否正确。

        Spring Security默认支持表单请求登录的源码,UsernamePasswordAuthenticationFilter.java
        

        步骤二:在把自定义的LakerUserService装载进去.

        @Autowired
        UserService userService;
        ...
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService);
        }
        

        步骤三:其中我们的业务用户User必须要实现UserDetails接口,并实现该接口中的 7 个方法:

        • getAuthorities():获取当前用户对象所具有的权限信息
        • getPassword():获取当前用户对象的密码

        返回的密码和用户输入的登录密码不匹配,会自动抛出 BadCredentialsException 异常。

        • getUsername():获取当前用户对象的用户名
        • isAccountNonExpired():当前账户是否未过期
        • isAccountNonLocked():当前账户是否未锁定
        • 返回了 false,会自动抛出 AccountExpiredException 异常。
        • isCredentialsNonExpired():当前账户密码是否未过期
        • isEnabled():当前账户是否可用
        @Data
        public class User implements UserDetails {
            private Integer id;
            private String username;
            private String password;
            private Boolean enabled;
            private Boolean locked;
            private List<String> authorities;
            @Override
            public String getPassword() {
                return password;
            }
            @Override
            public String getUsername() {
                return username;
            }
            @Override
            public boolean isAccountNonExpired() {
                return true;
            }
            @Override
            public boolean isAccountNonLocked() {
                return !locked;
            }
            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }
            @Override
            public boolean isEnabled() {
                return enabled;
            }
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                List<SimpleGrantedAuthority> authoritiesList = new ArrayList<>();
                for (String authority : authorities) {
                    authoritiesList.add(new SimpleGrantedAuthority(authority));
                }
                return authoritiesList;
            }
        }
        

        配置HttpSecurity Url访问权限

          /**
             * 配置 URL 访问权限
             */
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                //
                http    // 1.开启 HttpSecurity 配置
                        .authorizeRequests()
                        // laker/** 模式URL必须具备laker.query
                        .antMatchers("/laker/**").hasAnyAuthority("laker.query")
                        // 用户访问其它URL都必须认证后访问(登录后访问)
                        .anyRequest().authenticated()
                        .and()
                        // 2.开启表单登录,前后端分离的时候不用这个
                        .formLogin()
                        // 未登录时 重定向的url 默认是/login 内置的页面,可以自己自定义哈。一般前后端分离,不用这个
        //                .loginPage("/login")
                        //
        //                .defaultSuccessUrl("/user",true)
        //                .usernameParameter("username") // default is username
        //                .passwordParameter("password") // default is password
        //                .loginPage("/authentication/login") // default is /login with an HTTP get
        //                .failureUrl("/authentication/login?failed") // default is /login?error
        //                .loginProcessingUrl("/authentication/login/process") // default is /login
                        .and()
                        // 3.关闭csrf,前后端分离不需要这个。
                        .csrf().disable();
                        //授权码模式需要 会弹出默认自带的登录框
                        http.httpBasic();   
                		// 开启注销登录的配置 
                        http.logout()
                            // 配置注销登录请求URL为"/logout"(默认也就是 /logout)
                            .logoutSuccessUrl("/logout")
                            .clearAuthentication(true) // 清除身份认证信息
                            .invalidateHttpSession(true) // 使 session 失效;
            }
        
        • formLogin() 表示开启表单登录
        • **defaultSuccessUrl()**表示默认登录验证成功跳转的url,默认重定向到上次访问未成功的,如果没有则重定向到/.
        • loginProcessingUrl() 方法配置登录接口为“/login”,即可以直接调用“/login”接口,发起一个 POST 请求进行登录,登录参数中用户名必须为 username,密码必须为 password,配置 loginProcessingUrl 接口主要是方便 Ajax 或者移动端调用登录接口。

            anyRequest          |   匹配所有请求路径
            access              |   SpringEl表达式结果为true时可以访问
            anonymous           |   匿名可以访问 所有人都能访问,但是带上 token访问后会报错403
            denyAll             |   用户不能访问 所有人都能访问,包括带上 token 访问
            fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
            hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
            hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
            hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
            hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
            hasRole             |   如果有参数,参数表示角色,则其角色可以访问
            permitAll           |   用户可以任意访问
            rememberMe          |   允许通过remember-me登录的用户访问
            authenticated       |   用户登录后可访问

        自定义successHandler

        登录成功后默认是重定向url,我们可以自定义返回json用于前后端分离场景以及其他逻辑,例如成功之后发短信等。

        http.formLogin().successHandler((req, resp, authentication) -> {
            // 发短信哈
            Object principal = authentication.getPrincipal();
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(new ObjectMapper().writeValueAsString(principal));
            out.flush();
            out.close();
        })

        自定义failureHandler

        登录失败回调

        http.formLogin().failureHandler((req, resp, e) -> {
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write(e.getMessage());
            out.flush();
            out.close();
        })
        

        自定义未认证处理

        http.exceptionHandling()
        .authenticationEntryPoint((req, resp, authException) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("尚未登录,请先登录");
                    out.flush();
                    out.close();
                }
        );
        

        自定义权限不足处理

        http.exceptionHandling()
        				//没有权限,返回json
        				.accessDeniedHandler((request,response,ex) -> {
        					response.setContentType("application/json;charset=utf-8");
        					response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        					PrintWriter out = response.getWriter();
        					Map<String,Object> map = new HashMap<String,Object>();
        					map.put("code",403);
        					map.put("message", "权限不足");
        					out.write(objectMapper.writeValueAsString(map));
        					out.flush();
        					out.close();
        				})
        

        自定义注销登录

        .logout()
        .logoutUrl("/logout")
        .logoutSuccessHandler((req, resp, authentication) -> {
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.write("注销成功");
            out.flush();
            out.close();
        })
        

        前后端分离场景

        上面的都是入门的,实际项目中一般都是前后端分离的,在登录时都是自定义登录接口,例如登录接口是restful风格,增加了其他的验证码参数,还使用jwt来完成登录鉴权等。

        提供登录接口

        该接口需要在配置当中放行,未授权访问需要授权的请求时,会返回401或者403状态码,前端可以根据这个进行路由提示处理。

        @RestController
        public class LoginController {
           @Autowired
           LoginService ...
           @PostMapping("/login")
           public  login(@RequestBody Login login){
               ...
               return token;
           }
        }
        

        Service层创建UsernamePasswordAuthenticationToken对象,把用户名和密码封装成Authentication对象.

        @Service
        public class LoginServiceImpl implements LoginService {
            @Autowired
            private AuthenticationManager authenticationManager;
            @Autowired
            private UserDetailsService userDetailsService;
            @Override
            public  doLogin(Login login) {
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password;
                Authentication  authenticate
                try {         // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                     authenticate = authenticationManager.authenticate(authenticationToken);
                } catch (AuthenticationException e) {
                    e.printStackTrace();
                }
         
                if (Objects.isNull(authenticate)) {
                    //用户名密码错误
                    throw new ServicesException(...);
                }
                User authUser = (User) authenticate.getPrincipal();
                String token = JwtUtil.createJWT(username);
                Map<String, String> map = new HashMap<>();
                map.put("token", token);
                return map;
            }
        }
        

        自定义认证过滤器

        坊间有2种实现方式。

        方式一:继承UsernamePasswordAuthenticationFilter的写法需要使用登陆成功处理器、失败处理器等,还是需要按照security这一套来玩。

        Spring Security默认支持表单请求登录的源码UsernamePasswordAuthenticationFilter.java

        方式二:使用Filter的写法没有任何限制怎么玩都行,比如说添加其他参数验证码,返回json,token鉴权等。

        @Component
        public class LakerOncePerRequestFilter extends OncePerRequestFilter {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain) throws ServletException, IOException {
                String token = request.getHeader("Authorization");
                if (!StringUtils.isEmpty(token) )
                {
                    // 校验token ...
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, authorities;
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));                                     SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
                chain.doFilter(request, response);
            }
        }                                                                                                           //3、在UsernamePasswordAuthenticationFilter前添加认证过滤器
        http.addFilterBefore(lakerOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);
        

        鉴权

        1.注解鉴权

        • SpringSecurity配置类中开启方法级的认证
        • 使用 @PreAuthorize注解在方法或者类
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
        ...
        @RestController
        public class Controller {
            @GetMapping("/hello")
            @PreAuthorize("hasAnyAuthority('laker.query')")
            public String test() {
        }

        2.自定义Bean动态鉴权

        因为@PreAuthorize支持SpringEL表达式,所以可以支持自定义SpringBean动态鉴权。

        • 先自定义一个SpringBean。
        • 使用 @PreAuthorize注解在方法或者类配合@PreAuthorize(“@rbacService.hasPermission(‘xx’)”)
        @Component("rbacService")
        public class LakerRBACService {
            public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
                Object principal = authentication.getPrincipal();
                if (principal instanceof UserDetails) {
                    UserDetails userDetails=(UserDetails)principal;
        
                    /**
                     * 该方法主要对比认证过的用户是否具有请求URL的权限,有则返回true
                     */
                    //本次要访问的资源
                      SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getMethod() + "" + request.getRequestURI());
        
                    //用户拥有的权限中是否包含请求的url
                    return userDetails.getAuthorities().contains(simpleGrantedAuthority);
                }
                return false;
            }
                public boolean hasPermission() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                Object principal = authentication.getPrincipal();
                if (principal instanceof UserDetails) {
                    UserDetails userDetails = (UserDetails) principal;
                    /**
                     * 该方法主要对比认证过的用户是否具有请求URL的权限,有则返回true
                     */
                    //本次要访问的资源
                    HttpServletRequest request =((ServletRequestAttributes)
                            RequestContextHolder.getRequestAttributes()).getRequest();
        
                    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getRequestURI());
                    //用户拥有的权限中是否包含请求的url
                    return userDetails.getAuthorities().contains(simpleGrantedAuthority);
                }
                return false;
            }
        }
        // controller方法
        @PreAuthorize("@rbacService.hasPermission()")
        public String test() {
        }
        // 或者高级的全局url鉴权
        public class SecurityConfig extends WebSecurityConfigurerAdapter {
                 ...
              http.authorizeRequests() //设置授权请求,任何请求都要经过下面的权限表达式处理
                  .anyRequest().access("@rbacService.hasPermission(request,authentication)") //权限表达式     
        

        3.扩展默认方法自定义扩展根对象SecurityExpressionRoot

        https://www.jb51.net/article/245172.htm

        1.创建自定义根对象

        public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
            public CustomMethodSecurityExpressionRoot(Authentication authentication) {
                super(authentication);
            }
            /**
             * 自定义表达式
             * @param username 具有权限的用户账号
             */
            public boolean hasUser(String... username) {
                String name = this.getAuthentication().getName();
                HttpServletRequest request = ((ServletRequestAttributes)
                        RequestContextHolder.getRequestAttributes()).getRequest();
                String[] names = username;
                for (String nameStr : names) {
                    if (name.equals(nameStr)) {
                        return true;
                    }
                }
                return false;
            }
        }
        

        2.创建自定义处理器

        创建自定义处理器,主要是重写创建根对象的方法。

        public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
            @Override
            protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
                CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
                root.setThis(invocation.getThis());
                root.setPermissionEvaluator(getPermissionEvaluator());
                root.setTrustResolver(getTrustResolver());
                root.setRoleHierarchy(getRoleHierarchy());
                root.setDefaultRolePrefix(getDefaultRolePrefix());
                return root;
            }
        }
        

        3.配置GlobalMethodSecurityConfiguration
        之前我们使用@EnableGlobalMethodSecurity开启全局方法安全,而这些全局方法级别的安全配置就在GlobalMethodSecurityConfiguration配置类中。

        可以扩展这个类来自定义默认值,但必须确保在类上指定@EnableGlobalMethodSecurity 注解,否则会bean冲突报错。

        @Configuration
        // 将EnableGlobalMethodSecurity注解移到这里
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
            @Override
            protected MethodSecurityExpressionHandler createExpressionHandler() {
                return new CustomMethodSecurityExpressionHandler();
            }
        }

        4.controller使用自定义方法

        @PreAuthorize("hasUser('laker','admin')")
        public String test() {
            ...
        }
        

        登出

        http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {
                    // 删除用户token
            		...
                    // 返回json
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_OK);
                    PrintWriter out = response.getWriter();
                    out.write("OK");
                    out.flush();
                    out.close();
                });
        

        跨域

        @Override
        protected void configure(HttpSecurity http) throws Exception {
        	http.cors();//允许跨域,配置后SpringSecurity会自动寻找name=corsConfigurationSource的Bean
        	http.csrf().disable();//关闭CSRF防御
        }
        
        @Configuration
        public class CrosConfig {
            @Bean
            CorsConfigurationSource corsConfigurationSource() {
                final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
                CorsConfiguration cores=new CorsConfiguration();
                cores.setAllowCredentials(true);//允许客户端携带认证信息
                //springBoot 2.4.1版本之后,不可以用 * 号设置允许的Origin,如果不降低版本,则在跨域设置时使用setAllowedOriginPatterns方法
               // cores.setAllowedOrigins(Collections.singletonList("*"));//允许所有域名可以跨域访问
                cores.setAllowedOriginPatterns(Collections.singletonList("*"));
                cores.setAllowedMethods(Arrays.asList("GET","POST","DELETE","PUT","UPDATE"));//允许哪些请求方式可以访问
                cores.setAllowedHeaders(Collections.singletonList("*"));//允许服务端访问的客户端请求头
                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                cores.addExposedHeader(jsonWebTokenUtil.getHeader());
                // 注册跨域配置
                // 也可以使用CorsConfiguration 类的 applyPermitDefaultValues()方法使用默认配置
                source.registerCorsConfiguration("/**",cores.applyPermitDefaultValues());
                return source;
            }
        }
        

        全局配置

        @EnableGlobalMethodSecurity(prePostEnabled = true)
        @Configuration
        public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
            @Autowired
            UserService userService;
        
            @Autowired
            TokenFilter tokenFilter;
        
            /**
             * 配置 URL 访问权限
             */
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                //
                http    // 1.过滤请求
                        .authorizeRequests()
                        // 2.对于登录login 验证码captcha 允许访问
                        .antMatchers("/login").permitAll()
                        // 用户访问其它URL都必须认证后访问(登录后访问)
                        .anyRequest().authenticated()
                        .and()
                        // 3.关闭csrf
                        .csrf().disable()
                        // 4.基于token,所以不需要session
                        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        // 5.页面能不能以 frame、 iframe、 object 形式嵌套在其他站点中,用来避免点击劫持(clickjacking)攻击
                        .and().headers().frameOptions().disable();
                // 异常处理
                http.exceptionHandling()
                        // 未认证返回401
                        .authenticationEntryPoint((req, response, authException) -> {
                            response.setContentType("application/json;charset=utf-8");
                            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                            PrintWriter out = response.getWriter();
                            out.write("尚未登录,请先登录");
                            out.flush();
                            out.close();
                        })
                        // 没有权限,返回403 json
                        .accessDeniedHandler((request, response, ex) -> {
                            response.setContentType("application/json;charset=utf-8");
                            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                            PrintWriter out = response.getWriter();
                            Map<String, Object> map = new HashMap<String, Object>();
                            map.put("code", 403);
                            map.put("message", "权限不足");
                            out.write(JSONUtil.toJsonPrettyStr(map));
                            out.flush();
                            out.close();
                        });
                // 配置登出
                http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {
                        // 删除用户token
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_OK);
                    PrintWriter out = response.getWriter();
                    out.write("OK");
                    out.flush();
                    out.close();
                });
                // 添加JWT filter
                http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
            }
        
            /**
             * 配置用户及其对应的角色
             */
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userService);
            }
            /**
             * 适用于静态资源的防拦截,css、js、image 等文件
             * 配置的url不会保护它们免受CSRF、XSS、Clickjacking等的影响。
             * 相反,如果您想保护端点免受常见漏洞的侵害,请参阅configure(HttpSecurity)
             */
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/css/**", "/js/**");
            }
            @Bean
            PasswordEncoder passwordEncoder() {
                return new BCryptPasswordEncoder();
            }
            /**
             * 解决 无法直接注入 AuthenticationManager
             *
             */
            @Bean
            @Override
            public AuthenticationManager authenticationManagerBean() throws Exception
            {
                return super.authenticationManagerBean();
            }
        }
        

        参考:

        https://blog.csdn.net/X_lsod/article/details/122914659

        https://blog.csdn.net/godleaf/article/details/108318403

        https://blog.csdn.net/qq_43437874/article/details/119543579

        声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。