• SpringSecurity学习总结1入门


     

    我的学习视频连接:https://www.bilibili.com/video/BV14Q4y1o7nB?spm_id_from=333.999.0.0

    Spring Security 官网:https://spring.io/projects/spring-security#learn

    1. SpringSecurity入门

      Spring Security是一个高度自定义的安全框架。利用Spring IoC/DI和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。Spring Security的2个重要核心功能。“认证”是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序执行动作的其他系统),通俗点说就是系统认为用户是是否能登录。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲究是判断用户是否有权限去做某些事情。

    1.1 创建入门项目

      我们先新建一个maven项目,我们使用SpringBoott方式,引入以下必要的依赖。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
       
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

      编写一个SpringBoot项目的启动类

    @SpringBootApplication
    public class SpringSecurityApplication {
    
        public static void main(String[] args) {
            try {
                SpringApplication.run(SpringSecurityApplication.class, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

      在resources目录下,新建一个文件夹static,在static下创建一个 index.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        登录成功!!
    </body>
    </html>

    1.2 测试入门项目

      我们运行启动类 SpringSecurityApplication,当项目启动之后,启动日志里会有这样一句日志(说明SpringSecurity已经生效,默认登录用户名是user, 默认密码每次启动都不一样)

    Using generated security password: afa0c4a7-9108-42b7-9d40-b663920cb72d

       当项目启动后,输入请求地址:http://localhost:8080/   浏览器自动跳转到地址  http://localhost:8080/login 会看到一个登录页面。

      这个登录页面是SpringSecurity自带的,输入日志里打印的username和password之后,登录成功,浏览器会挑战到我们自己写的index.html页面。

    2. Spring Security的基础方法

    2.1 UserDetailsService接口

    package org.springframework.security.core.userdetails;
    
    public interface UserDetailsService {
       
        /**
         * Locates the user based on the username. In the actual implementation, the search
         * may possibly be case sensitive, or case insensitive depending on how the
         * implementation instance is configured. In this case, the <code>UserDetails</code>
         * object that comes back may have a username that is of a different case than what
         * was actually requested..
         *
         * @param username the username identifying the user whose data is required.
         *
         * @return a fully populated user record (never <code>null</code>)
         *
         * @throws UsernameNotFoundException if the user could not be found or the user has no
         * GrantedAuthority
         */
        UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }

      UserDetailsService接口是Spring Security的主要类,后面我们需要继承这个接口,来实现自己的业务逻辑。

      其中loadUserByUsername(String username)是用来实现登录逻辑的,参数username就是登录用户名,也就是刚才我们填写的user,如果用户名不存在会报UsernameNotFoundException异常,返回值是UserDetails对象。

    2.1 UserDetails接口

    package org.springframework.security.core.userdetails;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    
    import java.io.Serializable;
    import java.util.Collection;
    
    public interface UserDetails extends Serializable {
        
        Collection<? extends GrantedAuthority> getAuthorities();
    
        String getPassword();
    
        String getUsername();
    
        boolean isAccountNonExpired();
    
        boolean isAccountNonLocked();
    
        boolean isCredentialsNonExpired();
    
        boolean isEnabled();
    }

      UserDetails也是一个接口类,以后我们也可以让自己的用户类实现UserDetails的方法,来完成自己的业务逻辑。

      UserDetails继承了Serializable,说明可以被序列化。

      Collection<? extends GrantedAuthority> getAuthorities(); 此方法用于获取用户权限的,并且结果不能为null

      String getPassword();  获取用户的密码。

      String getUsername();  获取用户名。

      boolean isAccountNonExpired(); 判断账号是否未过期,过期的账号无法进行认证。

      boolean isAccountNonLocked();  判断账号是否未被锁定。被锁定的用户无法进行认证。

      boolean isCredentialsNonExpired(); 判断用户的凭证(密码)是否未过期,过期的凭据无法进行身份验证。

      boolean isEnabled(); 账号是否被启动,未启动的用户无法进行认证。

    2.3 User类

      UserDetails是一个接口,所以不能直接使用,SpringSecurity提供了一个实现类User

    package org.springframework.security.core.userdetails;
    
    public class User implements UserDetails, CredentialsContainer {
    
        private String password;
        private final String username;
        private final Set<GrantedAuthority> authorities;
        private final boolean accountNonExpired;
        private final boolean accountNonLocked;
        private final boolean credentialsNonExpired;
        private final boolean enabled;
    
        public User(String username, String password,
                Collection<? extends GrantedAuthority> authorities) {
            this(username, password, true, true, true, true, authorities);
        }
    
        public User(String username, String password, boolean enabled,
                boolean accountNonExpired, boolean credentialsNonExpired,
                boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
    
            if (((username == null) || "".equals(username)) || (password == null)) {
                throw new IllegalArgumentException(
                        "Cannot pass null or empty values to constructor");
            }
    
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.accountNonExpired = accountNonExpired;
            this.credentialsNonExpired = credentialsNonExpired;
            this.accountNonLocked = accountNonLocked;
            this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
        }
    }

      User继承了UserDetails,提供了UserDetails的7条属性和2个构造方法。User(String username, String password, Collection<? extends GrantedAuthority> authorities) 这三个参数的构造方法实际上是调用了下面7个参数的构造方法,传的参数就是username、password和authorities。

      当UserDetailsService的loadUserByUsername方法显示登录认证之后,会在数据库或内存中获取到username、password、authorities赋值给UserDetails的实现类User,并返回,实现了完整的登录逻辑。

    2.4 PsswordEncoder 密码加密

      接下来我们认识一下密码验证的核心接口 PasswordEncoder,

      密码解析器,接口,

    package org.springframework.security.crypto.password;
    
    public interface PasswordEncoder {
    
        String encode(CharSequence rawPassword);
    
        boolean matches(CharSequence rawPassword, String encodedPassword);
    
        default boolean upgradeEncoding(String encodedPassword) {
            return false;
        }
    }

      String encode(CharSequence rawPassword); 密码加密方法,参数rawPassword可以理解为客户端的明文密码,SpringSecurity推荐使用SHA-1或者Hash算法加密,Hash算法推荐使用8位字符或随机salt。

      boolean matches(CharSequence rawPassword, String encodedPassword); 密码匹配方法,rawPassword是明文密码,encodedPassword是加密后的密码,匹配这两个密码是否一致。

      default boolean upgradeEncoding(String encodedPassword) 二次加密方法,对已加密的密码,再次加密。默认返回false是不需要二次加密。

      PasswordEncoder是接口,它也有很多实现类,官方推荐使用的实现类是 BCryptPasswordEncoder

    public class BCryptPasswordEncoder implements PasswordEncoder {
        private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
        private final Log logger = LogFactory.getLog(getClass());
    
        private final int strength;
    
        private final SecureRandom random;
    
        public BCryptPasswordEncoder() {
            this(-1);
        }
    
        /**
         * @param strength the log rounds to use, between 4 and 31
         */
        public BCryptPasswordEncoder(int strength) {
            this(strength, null);
        }
    
        /**
         * @param strength the log rounds to use, between 4 and 31
         * @param random the secure random instance to use
         *
         */
        public BCryptPasswordEncoder(int strength, SecureRandom random) {
            if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
                throw new IllegalArgumentException("Bad strength");
            }
            this.strength = strength;
            this.random = random;
        }
    
        public String encode(CharSequence rawPassword) {
            String salt;
            if (strength > 0) {
                if (random != null) {
                    salt = BCrypt.gensalt(strength, random);
                }
                else {
                    salt = BCrypt.gensalt(strength);
                }
            }
            else {
                salt = BCrypt.gensalt();
            }
            return BCrypt.hashpw(rawPassword.toString(), salt);
        }
    
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            if (encodedPassword == null || encodedPassword.length() == 0) {
                logger.warn("Empty encoded password");
                return false;
            }
    
            if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
                logger.warn("Encoded password does not look like BCrypt");
                return false;
            }
    
            return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
        }
    }

      BCryptPasswordEncoder是一个强哈希加密方法。

      private final int strength;  此参数决定了密码强度,默认是10。如果是10以上的数字,密码会更加安全,但是会在加密过程消耗更多的性能。

      public String encode(CharSequence rawPassword) 对明文密码加密,加密时使用了随机salt(随机字符串),保证每次加密结果不一样。(注:如果没有使用随机salt,相同字符串加密后的结果是一样,就很容易猜到密码,很不安全)

      我们可以写个测试用例试一下,多运行几次会发现每次加密的结果是不一样的,这就是随机salt起作用了。

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = SpringSecurityApplication.class)
    public class SpringSecurityTest {
    
        @Test
        public void checkPassword(){
            PasswordEncoder pw = new BCryptPasswordEncoder();
            // 加密
            String encode = pw.encode("123");
            System.out.println("==== 加密后的密码:" + encode);
            // 比较密码
            boolean matches = pw.matches("123", encode);
            System.out.println("==== 比较密码:" + matches);
        }
    }

    3. 登录功能实现

    3.1 配置密码解析器

      当我们要实现自定义登录时,Spring容器内必须已经存在密码解析器,所以我们要提前把PasswordEncoder写到配置类里面去,让Spring来管理。

    @Configuration
    public class SecurityConfig {
    
        /**
         * 密码解析器
         */
        @Bean
        public PasswordEncoder getPasswordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
    }

    3.2 实现登录验证

      创建一个类,实现UserDetailsService接口。正常情况下用户密码都要从数据库查询的,我这里为了测试方便,直接写死的。

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 1. 根据用户名去数据库查询,如果不存在就抛UsernameNotFoundException异常
            if(!"admin".equals(username)){
                throw new UsernameNotFoundException("用户名不存在");
            }
            // 2. 比较密码(注册时已经加密过,如果匹配成功返回UserDetails)
            String password = passwordEncoder.encode("123");
            // 正常逻辑是需要使用 passwordEncoder.matches() 方法来验证密码的是否正确的。
    
            return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
        }
    }

      配置完毕后,我们再次启动应用程序,发现日志里不打印默认用户名密码的,在登录页面只能输入 username=admin, password=123 才可以登录。

    3.3 默认参数

      在SpringSecurity中,默认的用户名和密码参数就是username和password,只支持POST请求,原因在于SpringSecurity实现了一个叫UsernamePasswordAuthenticationFilter 的拦截器,只会获取username和password的值,取不到只就会默认为空字符串,所以验证失败。有兴趣的同学可以自己看看这个类。

    package org.springframework.security.web.authentication;
    
    public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
        private boolean postOnly = true;
    ...... }

      如果我们非要改换成其他参数的话,可以在SecurityCofnig配置类里实现一个 configure 方法,用于一些SpringSecurity的配置。下面的配置就把接收用户名和密码的参数换成了username123和password123。

      其他的一些配置是在前后端未分离的情况下,一些授权和跳转的设置。

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 表单提交
            http.formLogin()
                    // 自定义入参
                    .usernameParameter("username123")
                    .passwordParameter("password123")
    
                    .loginPage("/login.html") // 自定义登录页面
                    // 必须和登录页面的方法一样,会执行自定义登录逻辑
                    .loginProcessingUrl("/login")
                    // 登录成功跳转的页面,POST请求,toMain方法会跳转到main.html页面 
                    .successForwardUrl("/toMain")
                    // 登录失败跳转的页面,POST请求,toError方法会跳转到error.html页面
                    .failureForwardUrl("/toError");
            // 授权设置
            http.authorizeRequests()
                    // 放行/error.html 不需要认证
                    .antMatchers( "/error.html").permitAll()
                    // 放行/login.html 不需要认证
                    .antMatchers("/login.html").permitAll()
                    // 所有请求都必须认证才能访问,必须登录
                    .anyRequest().authenticated();
            // 关闭crsf防护
            http.csrf().disable();
        }

    3.4 自定义跳转逻辑

      在上面的配置里,登录成功后,跳转设置是 successForwardUrl("/toMain"),表示登录成功后调用/toMain方法,实现内部页面的跳转。假设我们想跳转到 http://www.baidu.com ,这样的设置就不生效了。

      点击lsuccessForwordUrl这个方法,我们看到里面使用的是一个登录成功的拦截器 ForwardAuthenticationSuccessHandler

        public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) {
            this.successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
            return this;
        }

      所以我们需要自定义一个登录成功的拦截器

    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
        // 跳转的url
        private String url;
    
        // 构造方法
        public MyAuthenticationSuccessHandler(String url) {
            this.url = url;
        }
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 重定向
            response.sendRedirect(url);
        }
    }

      然后调整一下配置内容。把原来的successForwardUrl注释掉,使用successHandler

         // .successForwardUrl("/toMain")
         .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))

      我们再来看这个自定义登录成功的拦截器参数,有一个Authentication参数,用户登录成功之后可以拿到登录用户的信息,方法如下:

    public interface Authentication extends Principal, Serializable {
    
        /**
         * 用户的权限列表
         */
        Collection<? extends GrantedAuthority> getAuthorities();
    
        /**
         * 或者用户凭证(密码),但因为安全设置这个值一般的null
         */
        Object getCredentials();
    
        /**
         * 获取详情
         */
        Object getDetails();
    
        /**
         * 获取UserDetails的实现类,用户详情
         */
        Object getPrincipal();
    
       /**
         * 是否被认证
         */
        boolean isAuthenticated();
    
       /**
         * 设置认证状态
         */
        void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
    }

      而登录失败的跳转配置 failureForwardUrl 里面也是实现了一个登录失败的拦截器 ForwardAuthenticationFailureHandler,业务逻辑基本一样的。

        public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
            this.failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
            return this;
        }

    4. 授权配置

    4.1 授权

      上面在SecurityConfig类的configure 方法中,已经添加了一些授权设置。

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            .....
            // 授权设置
            http.authorizeRequests()
                    // 放行/error.html 不需要认证
                    .antMatchers( "/error.html").permitAll()
                    // 放行/login.html 不需要认证
                    .antMatchers("/login.html").permitAll()
                    // 所有请求都必须认证才能访问,必须登录(这行代码必须放到最后面)
                    .anyRequest().authenticated();
            // 关闭crsf防护
            http.csrf().disable();
        }

      

      anyRequest()  表示匹配所有的请求,一般情况下此方法都会使用,设置全部内容都需要进行认证。

      anyMatcher()  表示要设置不需要认证的地址,方法定义如下:

    public C antMatchers(String... antPatterns) {

      参数是不定向参数,每个参数是一个ant表达式,用于匹配URL规则。

      规则如下:

    ?   匹配一个字符
    *   匹配0个或多个字符
    **  匹配0个或多个目录

      在实际项目中经常需要放行的所有静态资源,西面演示放行js文件夹所有脚本文件。

    .antMatchers("/js/**", "/css/**", "/images/**").permitAll()

      还有一种匹配方式是只要是.js文件都放行

    .antMatchers("/**/*.js").permitAll()

      regexMatchers() 正则表达式,指定要放行的资源或目录

    .regexMatchers(".+[.]png").permitAll()

      在regexMatchers 和 antMetchers 里还可以指定请求Http请求类型

    .regexMatchers(HttpMethod.POST, ".+[.]png").permitAll()

      mvcMetchers()  mvc匹配

    .mvcMatchers("/demo").servletPath("/zh").permitAll()

      permitAll() 在上面增加匹配时都有permitAll这个方法,点击进去我们看到有几种选项

        static final String permitAll = "permitAll";  // 允许所有
        private static final String denyAll = "denyAll";  // 禁止所有
        private static final String anonymous = "anonymous";   // 匿名,类似于permitAll,指不需要登录可以的页面,比如首页
        private static final String authenticated = "authenticated";   // 授权
        private static final String fullyAuthenticated = "fullyAuthenticated"; // 必须账号密码登录授权
        private static final String rememberMe = "rememberMe";  // 记住我

    4.2 权限控制

      在前面的UserDetailsServiceImpl中,我们设置了用户登录后拥有的权限

    return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));

      我们可以指定拥有权限的用户,才能访问指定的页面

     // 权限控制,严格区分大小写
     .antMatchers("/main1.html").hasAuthority("admin")
     // 匹配2个权限的任意一个
     .antMatchers("/main1.html").hasAnyAuthority("admin", "admin2")

    4.3 角色控制

      在UserDetailsServiceImpl中,用户登录成功后是可以自定角色的。注意:角色区别于权限,必须大写的ROLE_ 开头。

     return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));

      上面的例子中,我们指定了一个abc的角色。

    // 角色控制,严格区分大小写
    .antMatchers("/main1.html").hasRole("abc")
    // 角色控制,严格区分大小写
    .antMatchers("/main1.html").hasAnyRole("abc", "abc2")

    4.4 IP地址控制

      用于指定IP地址允许访问页面

    // 基于IP地址控制
    .antMatchers("/main1.html").hasIpAddress("127.0.0.1")

    4.5 403异常拦截

      按照前面的例子中,如果用户没有权限,就会展示SpringSecurity默认的403页面,比较难看。我们可以配置一个自定义的403异常拦截器。

    @Component
    public class MyAccessDeniedHandler implements AccessDeniedHandler {
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            // 响应状态
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            // 返回json格式
            response.setHeader("Content-Type", "application/json;charset=utf-8");
            // 返回消息
            PrintWriter writer = response.getWriter();
            writer.write("{\"status\":\"error\",\"mgs\":\"权限不足,请联系管理员\"}");
            writer.flush();
            writer.close();
        }
    }

      把这个403异常拦截器配置到SecurityConfig中

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private MyAccessDeniedHandler accessDeniedHandler;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            ......
            // 异常拦截
            http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
            // 关闭crsf防护
            http.csrf().disable();
        }
    }

    4.6 access表达式

      在以上4.1、4.2、4.3讲到的所有控制方法,本质上都是access,比如我们点击permitAll方法查看

    public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry permitAll() {
        return this.access("permitAll");
    }

      在Spring Security官网上提供了access表达式:

       我们之前的权限控制可以改成这样的写法

            .antMatchers("/login.html").access("permitAll")
            .antMatchers("/main1.html").access("hasRole('abc')")

    4.7 自定义访问控制

      我们先创建一个权限验证的Service,通过用户已授权的的Grantedauthority来判断用户的相关权限。

    @Service
    public class MyServiceImpl implements MyService {
    
        @Override
        public boolean hashPermission(HttpServletRequest request, Authentication authentication) {
            // 获取主体
            Object obj = authentication.getPrincipal();
            // 判断主体是否属于UserDetails
            if(obj instanceof UserDetails) {
                // 获取权限,集合是GrantAuthority泛型
                UserDetails userDetails = (UserDetails) obj;
                Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
                // 判断请求的url是否在权限里
                return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
            }
            return false;
        }
    
    }

      自定义访问权限的使用方式

    // 自定义access方法
    .anyRequest().access("@myServiceImpl.hashPermission(request, authentication)")

    5 基于注解的访问控制

      在Spring Security中提供了一些访问控制的注解。这些注解都是默认是不可用的,需要通过@EnableGlobalMethodSecurity记性开启后使用。

      如果设置的条件允许,程序正常执行,如果不允许会报500

      注意:注解和SecurityConfigure类中的access配置会有冲突,所以只建议使用其中一个。

    org.springframework.security.access.AccessDeniedException: 不允许访问

      这些注解可以写到Service接口或方法上,也可以写到Controller或Conroller的方法上。通常情况下都是写在控制器方法上的,控制接口URL是否允许被访问。

    5.1 @Secured

      @Secured是专门用于判断是否具有角色的。能写在方法或类上。参数要以ROLE_开头。

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Secured {
        /**
         * Returns the list of security configuration attributes (e.g.&nbsp;ROLE_USER, ROLE_ADMIN).
         *
         * @return String[] The secure method attributes
         */
        public String[] value();
    }

      在启动类中开启@Secured注解

    @EnableGlobalMethodSecurity(securedEnabled = true)
    @SpringBootApplication
    public class SpringSecurityApplication {
    
    }

      使用@Secured注解。我们在Controller中的main方法中使用了@Secured注解,并指定角色abc可以访问。

        @Secured("ROLE_abc")
        @PostMapping("/toMain")
        public String main(){
            return "redirect:main.html";
        }

    5.2 @PreAuthorize / @ PostAuthorize

      @PreAuthorize 和 @PostAuthorize 都是方法或类级别的注解。

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface PreAuthorize {
        /**
         * @return the Spring-EL expression to be evaluated before invoking the protected
         * method
         */
        String value();
    }

      @PreAuthorize 表示访问方法或类在执行之前判断权限,大多情况下毒是用这个注解,注解的参数和access()方法参数取值相同,都是权限表达式。

      @PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用。

      在启动类上开启 @PreAuthorize注解

    @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

      在方法使用@PreAuthorize注解

        @PreAuthorize("hasRole('abc')")
        @PostMapping("/toMain")
        public String main(){
            return "redirect:main.html";
        }

    6 退出登录

      用户只需要向Spring Security项目中发送/logout 退出请求即可。

    6.1 退出登录

      实现退出非常简单,只需要在页面总添加 /logout 的超链接

    <a href="/logout">退出登录</a>

      为了实现更好的效果,通常添加退出的配置。默认的退出url为 /logout, 退出成功后跳转到 /login?logout

      还需要在SecurityConfig中添加退出配置

    http.logout().logoutSuccessUrl("/login.html");

      自定义退出后地址

    http.logout().logoutUrl("/user/logout");

    7. SpringSecurity中的CSRF

      在刚开始介绍Spring Security时,在配置中一直存在这样一行代码:http.csrf().disable(); 如果没有这行代码导致用于无法被认证。这行代码的含义是:关闭csrf防护。

    7.1 什么是CSRF ?

      CSRF(Cross-site request forgery) 跨站请求伪造,也被称为“OneClick Attack” 或者 "Session Riding"。通过伪造用户请求访问受信任站点的非法请求访问。

      跨域:只要网络协议,IP地址,端口中任何一个相同就是跨域请求。

      客户端和服务进行交互时,由于http协议本身是无状态协议,所以引入了cookie进行记录客户端身份。在cookie中会存放session id 用来识别客户端身份的。在跨域的情况下,session id 可能被第三方恶意劫持,通过这个session id 向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

    7.2 Spring Security中的CSRF

      从Spring Security4开始CSRF防护默认开启。默认会拦截请求,进行CSRF处理。CSRF为了保护不是其他第三方网站访问,要求访问时携带参数名为 _csrf 值为token(token在服务端产生)的内容,如果token和服务端是token匹配成功,则正常访问。

     

  • 相关阅读:
    CSPS模拟 57
    CSPS模拟 56
    CSPS Oct目标
    CSPS模拟 55
    CSPS模拟 54
    CSPS模拟 53
    和manacher有关的乱写
    CSPS模拟 52
    CSPS模拟 51
    Git和代码规范
  • 原文地址:https://www.cnblogs.com/huanshilang/p/15592479.html
Copyright © 2020-2023  润新知