欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Security基础内容

程序员文章站 2022-07-12 19:17:11
...


Spring Security基础内容

刚接触,试图理解

资源

官方文档与API
学习文章


创建

依赖导入

<dependencies>
    <!-- ... other dependency elements ... -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

指定版本
不指定就用默认默认设置的版本

<properties>
    <!-- ... -->
    <spring-security.version>5.3.4.RELEASE</spring-security.version>
</dependencies>

特征

  • Authentication:鉴定,验证正在登录者的身份。
  • Authorization:授权
  • PasswordEncoder接口,像是存储密码的地方

SpringBoot自动做了三件事

  • 启用默认配置,创造了个过滤器bean,命名为springSecurityFilterChain,这个bean对所有的安全负责。如URLs、验证用户名和密码、重定向等。
  • 创建了一个UserDetailsServciebean,用户名是user,密码是随机的可以在consolelog看到。
  • 注册了一个过滤器bean,命名为springSecurityFilterChain,是每个requestServlet容器。

结构
现在要做验证和鉴权的第一步:
Spring Security基础内容

创建自定义用户
使用AuthenticationManagerBuilderinMemoryAuthentication()方法在内存中创建,可设置用户名、密码、指定权限,注意密码需要加密。

        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");

完整代码
configure(HttpSecurity)部分可以看后面的解析。

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    // 利用参数不同做重载,auth是创建用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密
         */
        // 看了文档还是有点用的,passwordEncoder是密码库,有不同的加密方式
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER");
        /**
         * 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限
         */
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
    }
}

验证

Spring Security基础内容


自定义验证方法

  1. 首先,要能接收到前端输入的用户名和密码,并存储起来。
    一个重要的实例
    将前端获取的用户的凭证(也就是密码)、细节、职务等信息保存起来,第一个方法就是获取用户的权限信息集合。

    public interface Authentication extends Principal, Serializable {
        Collection<? extends GrantedAuthority> getAuthorities();
    
        Object getCredentials();
    
        Object getDetails();
    
        Object getPrincipal();
    
        boolean isAuthenticated();
    
        void setAuthenticated(boolean var1) throws IllegalArgumentException;
    }
    
  2. 有了用户信息之后,就可以进行验证了,这里自定义一个验证方法。
    自定义验证类
    功能是只要用户名是alex,就能通过验证,并加上ADMINUSER权限。

    /**
     * Created by wxb on 2018/10/21 0021.
     * 自定义验证类,可以用作backdoor,例如输入:alex/任意密码就可以通过验证
     */
    @Component
    public class BackdoorAuthenticationProvider implements AuthenticationProvider {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            // 这个方法是Principal的
            String name = authentication.getName();
            String password = authentication.getCredentials().toString();
    
            //利用alex用户名登录,不管密码是什么都可以,伪装成admin用户
            if (name.equals("alex")) {
                // 自己造了个,按理说应是getAuthorities()方法?
                Collection<GrantedAuthority> authorityCollection = new ArrayList<>();
                authorityCollection.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
                authorityCollection.add(new SimpleGrantedAuthority("ROLE_USER"));
                // 设置职务、密码、权限
                // 有几个方法确实是继承了Authentication
                return new UsernamePasswordAuthenticationToken(
                        "admin", password, authorityCollection);
            } else {
                return null;
            }
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return authentication.equals(
                    UsernamePasswordAuthenticationToken.class);
        }
    }
    
  3. 有了用户和自定义的验证方法,就可以走主验证程序了,要把自己定义的验证方法加进去。
    主验证程序
    通过重写参数为AuthenticationManagerBuilder类型的configure,可以创建用户,同时添加自己设置的验证类,上面用了@Component注释,所以可以自动装箱,加入到验证链ProviderManagerBuilder中(AuthenticationManagerBuilder实现了ProviderManagerBuilder)。

    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Autowired
        BackdoorAuthenticationProvider backdoorAuthenticationProvider;
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            /**
             * 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密
             */
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER");
            /**
             * 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限
             */
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
            //将自定义验证类注册进去
            auth.authenticationProvider(backdoorAuthenticationProvider);
        }
    }
    
  4. 验证完了以后,验证信息会存在SecurityContextHolder中,可以直接其中的Authentication对象。
    获取验证信息
    controller层:

    /**
     * 定义用户相关网址映射的Controller
     */
    @Controller
    public class UserController {
    
        @RequestMapping("/user")
        public String user(@AuthenticationPrincipal Principal principal, Model model){
            model.addAttribute("username", principal.getName());
    
            //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端
            Authentication  auth = SecurityContextHolder.getContext().getAuthentication();
            Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities();
            model.addAttribute("authorities", authorityCollection.toString());
            return "user/user";
        }
        @RequestMapping("/admin")
        public String admin(@AuthenticationPrincipal Principal principal, Model model){
            model.addAttribute("username", principal.getName());
    
            //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端
            Authentication  auth = SecurityContextHolder.getContext().getAuthentication();
            Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities();
            model.addAttribute("authorities", authorityCollection.toString());
            return "admin/admin";
        }
    }
    

流程顺一遍
先是index页面提交,然后跳转/login,请求被SecurityConfiguration类的confiure(HttpSecurity)方法捕获,之后会进行验证,走configure(AuthenticationManagerBuilder auth),这里可以不通过数据库添加用户,还可以添加自定义的验证类BackdoorAuthenticationProvider,然后就会用接收的usernamepassword包装成的Authentication类对所有的验证类进行验证,只要有一个能通过就行。


使用数据库进行验证

两个关键接口

  • Security提供了两个核心接口UserDetailsUserDetailsService
  • UserDetails定义了验证信息的详细方法。
  • UserDetailsService的实现类中的方法loadUserByUsername通过用户名读取用户信息(这里需要用到操作数据的Mapper),返回一个UserDetails的实现类
  • 之后,这个UserDetailsService就要作为验证链的一员了,添加方法和前面的也不一样:
            //加入数据库验证类,下面的语句实际上在验证链中加入了一个DaoAuthenticationProvider
            auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    

UserDetails的实现类

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Created by wxb on 2018/10/23 0023.
 * 用户表user对应的类,同时实现了UserDetails接口,成为登录验证的信息类
 */
@Component
public class MyUserBean implements UserDetails {
    private Long id;
    private String name;
    private String address;
    private String username;
    private String password;
    private String roles;

    /**
     * 从数据库中取出roles字符串后,进行分解,构成一个GrantedAuthority的List返回
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 对数据库中的内容进行解析,返回一个权限类的数组
        String[] authorities = roles.split(",");
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        for (String role : authorities) {
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role));
        }
        return simpleGrantedAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

UserDetailsService的实现类

import com.apkkids.bean.MyUserBean;
import com.apkkids.mapper.MyUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * Created by wxb on 2018/10/23 0023.
 * UserDetailsService的实现类,用于在程序中引入一个自定义的AuthenticationProvider,实现数据库访问模式的验证
 *
 */
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    MyUserMapper mapper;
    // 接口自带方法,返回一个UserDetails的实现类
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MyUserBean userBean = mapper.selectByUsername(username);
        if (userBean == null) {
            throw new UsernameNotFoundException("数据库中无此用户!");
        }
        return userBean;
    }
}

添加到验证链中
最下面那句就是关键,又加了一种验证方法,可以查询数据库内容,其他地方都差一样。

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    BackdoorAuthenticationProvider backdoorAuthenticationProvider;
    @Autowired
    MyUserDetailsService myUserDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密
         */
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER");
        /**
         * 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限
         */
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
        //将自定义验证类注册进去
        auth.authenticationProvider(backdoorAuthenticationProvider);
        //加入数据库验证类,下面的语句实际上在验证链中加入了一个DaoAuthenticationProvider
        auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

鉴权

  1. 首先要有权限,如何获得知道网页对应的权限?实现FilterInvocationSecurityMetadataSource类:
    MySecurityMetadataSource
    从前端获取url,根据url来判断属于那种权限地址。

    @Component
    public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
        @Autowired
        ResourceMapper resourceMapper;
        //本方法返回资访问源所需的角色集合
        AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            // 从object中得到需要访问的资源,即网址
            // 这个应是网页输入、跳转
            String requestUrl = ((FilterInvocation) object).getRequestUrl();
            // 从数据库中得到所有资源,以及对应的角色
            List<MyResourceBean> resourceBeans = resourceMapper.selectAllResource();
            for (MyResourceBean resource : resourceBeans) {
                // 首先进行地址匹配
                // url对上了,且有对应角色; 看这样子URL是不能重复的
                if (antPathMatcher.match(resource.getUrl(), requestUrl)
                        && resource.getRolesArray().length > 0) {
                    return SecurityConfig.createList(resource.getRolesArray());
                }
            }
            //匹配不成功返回一个特殊的ROLE_NONE
            return SecurityConfig.createList("ROLE_NONE");
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    }
    
  2. 知道了网站需要的权限,就要看该用户有没有这种权限,用户的权限可以通过实现了UserDetailsMyUserBean获得,然后将用户的权限和网站跳转需要的权限匹配,具体是实现AccessDecisionManager接口。
    MyAccessDecisionManager

    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
                throws AccessDeniedException, InsufficientAuthenticationException {
            // 从authentication中获取当前用户具有的权限,一个角色可能有多种权限
            Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
    
            // 从configAttributes中获取访问资源所需要的权限,它来自MySecurityMetadataSource的getAttributes
            // 有的资源多个权限都可以访问
            Iterator<ConfigAttribute> iterator = configAttributes.iterator();
            // 所以这里就是:用户的多个权限——匹配——可查看该资源的多个权限
            while (iterator.hasNext()) {
                // 这属性是权限吧,为啥是两步?
                // 答:类就是这么设计的
                ConfigAttribute attribute = iterator.next();
                //  获取角色信息了
                String role = attribute.getAttribute();
    
                // 匹配不上
                if ("ROLE_NONE".equals(role)) {
                    if (authentication instanceof AnonymousAuthenticationToken) {
                        throw new BadCredentialsException("用户未登录");
                    } else
                        return;
                }
                // 逐一对角色拥有的权限进行匹配
                for (GrantedAuthority authority : userAuthorities) {
                    if (authority.getAuthority().equals("ROLE_ADMIN")) {
                        return; //用户具有ROLE_ADMIN权限,则可以访问所有资源
                    }
                    if (authority.getAuthority().equals(role)) {
                        return;  //匹配成功就直接返回
                    }
                }
            }
            //不能完成匹配
            throw new AccessDeniedException("你没有访问" + object + "的权限!");
        }
    
        @Override
        public boolean supports(ConfigAttribute attribute) {
            return true;
        }
    
        @Override
        public boolean supports(Class<?> clazz) {
            return true;
        }
    }
    
  3. 如果匹配不上的话,会抛出一个AccessDeniedException异常,这个异常被AccessDeniedHandler接口的实现类MyAccessDeniedHandler接收,可以生成一个json对象。
    MyAccessDeniedHandler

    // 处理没通过的请求
    @Component
    public class MyAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter out = response.getWriter();
            ResponseBean info = new ResponseBean(500, accessDeniedException.getMessage(), null);
            out.write(new ObjectMapper().writeValueAsString(info));
            out.flush();
            out.close();
        }
    }
    
  4. 方法都写完了,剩下的就是绑定了,即在WebSecurityConfigurerAdapter接口的实现类SecurityConfiguration中配置MyAccessDecisionManagerMySecurityMetadataSourceMyAccessDeniedHandler。使用.withObjectPostProcessor,这个方法名字也挺好记的,对象传递处理器
    SecurityConfiguration

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    // 注意这个with方法,绑定网页权限分析和匹配决策方法
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            object.setSecurityMetadataSource(mySecurityMetadataSource);
                            object.setAccessDecisionManager(myAccessDecisionManager);
                            return object;
                        }
                    })
                    .and()
                    .formLogin().loginPage("/login_p").loginProcessingUrl("/login").permitAll()
                    //1.自定义参数名称,与login.html中的参数对应
                    .usernameParameter("myusername").passwordParameter("mypassword")
                    .and()
                    .logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll()
                    // 后面这些是处理匹配不上的情况
                    .and()
                    .csrf().disable()
                    .exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
        }
    }
    

VBlog项目实例

项目地址
项目代码理解
重点就在configure(HttpSecurity http)的分析中。

代码

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 简简单单,只用了数据库
        auth.userDetailsService(userService);
    }

    // 登录的时候走了这里
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // authenticated是已认证的意思
                .antMatchers("/admin/category/all").authenticated()
                .antMatchers("/admin/**","/reg").hasRole("超级管理员")///admin/**的URL都需要有超级管理员角色,如果使用.hasAuthority()方法来配置,需要在参数中加上ROLE_,如下.hasAuthority("ROLE_超级管理员")
                // 任意URL,之所以可以放任意,是因为后面有找不到的操作方法
                .anyRequest().authenticated()//其他的路径都是登录后即可访问
                // 这个成功加了新东西
                // formLogin()方法之后,链式就不再是HttpSecurity了,而是FormLoginConfigurer<H>
                // successHandler指定成功的时候做的事情,并不影响跳转,就像是插进去的一样
                .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                httpServletResponse.setContentType("application/json;charset=utf-8");
                PrintWriter out = httpServletResponse.getWriter();
                out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
                out.flush();
                out.close();
            }
        })
                // 指定失败的时候该做的事情
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
                        out.flush();
                        out.close();
                    }
                }).loginProcessingUrl("/login")
                .usernameParameter("username").passwordParameter("password").permitAll()
                // 失败方法和知乎是一样的
                .and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/blogimg/**","/index.html","/static/**");
    }

    // 指定,自动装箱写后面了
    @Bean
    AccessDeniedHandler getAccessDeniedHandler() {
        return new AuthenticationAccessDeniedHandler();
    }
}

URL匹配与保护

应是总开关
authorizeRequests:作用是允许限制访问基于HttpServletRequest使用RequestMatcher实现(即通过URL模式)。这意思就是开启路径限制?返回类型还挺独特的ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>

匹配
requestMatchers():配置一个request Mather数组,参数为RequestMatcher 对象,其match 规则自定义,需要的时候放在最前面,对需要匹配的的规则进行自定义与过滤

antMatchers():配置一个request Mather 的 string数组,参数为 ant 路径格式, 直接匹配url

anyRequest():匹配任意url,无参 ,最好放在最后面

保护/限制
我觉得叫限制更合适
authenticated():保护UrL,需要用户登录

hasRole(String role):限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较. 另一个方法是hasAuthority(String authority)

hasAnyRole(String… roles):允许多个角色访问. 另一个方法是hasAnyAuthority(String… authorities)


configure(AuthenticationManagerBuilder auth)分析

这个就是配置验证链,比较简单:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 简简单单,只用了数据库
        auth.userDetailsService(userService);
    }

configure(HttpSecurity http)分析

就按着下面的链式调用方法展开:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // authenticated是已认证的意思
                .antMatchers("/admin/category/all").authenticated()
                .antMatchers("/admin/**","/reg").hasRole("超级管理员")///admin/**的URL都需要有超级管理员角色,如果使用.hasAuthority()方法来配置,需要在参数中加上ROLE_,如下.hasAuthority("ROLE_超级管理员")
                // 任意URL,之所以可以放任意,是因为后面有找不到的操作方法
                .anyRequest().authenticated()//其他的路径都是登录后即可访问
                // 这个成功加了新东西
                // formLogin()方法之后,链式就不再是HttpSecurity了,而是FormLoginConfigurer<H>
                // successHandler指定成功的时候做的事情,并不影响跳转,就像是插进去的一样
                .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                httpServletResponse.setContentType("application/json;charset=utf-8");
                PrintWriter out = httpServletResponse.getWriter();
                out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
                out.flush();
                out.close();
            }
        })
                // 指定失败的时候该做的事情
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
                        out.flush();
                        out.close();
                    }
                }).loginProcessingUrl("/login")
                .usernameParameter("username").passwordParameter("password").permitAll()
                .and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    }

formLogin():调用了formLogin()后,没参数的话就变类型了。作用是指定支持基于表单的身份验证(是不是从网页接收信息来的就是基于表单?),如果没指定后面的loginPage()的话,就会自动生成一个(自动生成应该就是默认/login吧)

  • 无参情况返回FormLoginConfigurer<HttpSecurity>类型,即用这个类型进行配置。
  • 有参情况返回HttpSecurity,即已经用参数配置完了, 再跳回去
  • 如果没指定位置,那么默认跳到/login,失败就重定向到'/login?error

loginPage():方法是属于FormLoginConfigurer<H>的,而loginPage()指定登陆需要的位置,如果使用的WebSecurityConfigurerAdapter,那么会默认指定一个(一般都用了吧)。

successHandler:作用是当验证成功的时候运行这一块,是继承(AbstractAuthenticationFilterConfigurer)的方法,参数是接口AuthenticationSuccessHandleronAuthenticationSuccess方法,用的是没有过滤链的重载方法。onAuthenticationSuccess当通过验证的时候才能被调用。

failureHandler:跟上面对应,验证失败的时候调用。

loginProcessingUrl:也是继承的,作用是指定验证凭证(credentials)的URL,参数是用来验证用户和密码的URL地址,然后通过usernameParameterpasswordParameter来指定来自HTTP的用来验证的参数名。

permitAll():无参数,相当于permitAll(true),true表示授予对url的访问权,false表示跳过此步骤,用来确保所有的用户都能访问某些页面;指定URL无需保护,一般应用与静态资源文件

感觉像个总开关?

and():继承自SecurityConfigurerAdapter,功能是如果使用了SecurityConfigurer就返回SecurityBuilder(肯定用了吧,没用写他干嘛),返回类型是SecurityBuilder(官方文档说有利于方法链接,确实是,这样就能从一个新的起点开始了)。
但是SecurityBuilder里面啥方法也没有啊??
感觉这是一套终极继承关系来的!

  1. public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
  2. public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
  3. public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
  4. public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<LogoutConfigurer<H>, H> {
  5. public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {

最终在HttpSecurity里找到了!

    public LogoutConfigurer<HttpSecurity> logout() throws Exception {
        return (LogoutConfigurer)this.getOrApply(new LogoutConfigurer());
    }

所以,这个方法返回的并不是SecurityBuilder,而是他的子类!也就是最开始的HttpSecurity!破案成功。

logout():退出的时候用的,指定URL地址,默认是/logout,功能是让HTTP的Session无效,同时清除rememberMe()SecurityContextHolder(这个里面也有用户名和密码,用getContext方法可以得到),并且重定向到/login?success

csrf():开启CSRF支持,如果用了WebSecurityConfigurerAdapter的话会默认开启,然后后面加个.disable()就可以取消开启。disable()返回的类型是HttpSecurityBuilder,而csrf()返回的类型是CsrfConfigurer,所以不使用的话就直接返回最初了,使用的话就紧接着配置csrf

返回HttpSecurityBuilder:
这些返回最初的方法都是用的this.getBuilder()
and()中:

    public B and() {
        return this.getBuilder();
    }

disable()

    public B disable() {
        ((HttpSecurityBuilder)this.getBuilder()).removeConfigurer(this.getClass());
        return (HttpSecurityBuilder)this.getBuilder();
    }

所以作为整个类,WebSecurityConfig extends WebSecurityConfigurerAdapter,这个类的getBuilder就已经指定为configure(HttpSecurity http)中的那个参数了。

exceptionHandling:是添加异常处理的方法,然后返回一个异常处理配置器ExceptionHandlingConfigurer,在调用其中的accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)方法来指定异常处理器,就是自己定义的那个,这个方法返回的还是ExceptionHandlingConfigurer,不过到这里整个方法也结束了,如果想继续加东西,也可以再用and()跳回去。


configure(WebSecurity web)分析

配置一些忽略内容:

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/blogimg/**","/index.html","/static/**");
    }

ignoring():作用是允许添加Spring Security应该忽略的RequestMatcher实例,而RequestMatcher是匹配HttpServletRequest的简单策略。返回的类型是WebSecurity.IgnoredRequestConfigurer,到这里也比较清晰了,就是对忽略的内容进行配置。配置的内容都不会被HttpServletRequest获取,一般来说要忽略的都是静态资源,还有一些动态requests,将这些request映射为允许所有用户请求的样子。

相关标签: 开发技能