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匹配成功,则正常访问。

  我感觉这个没啥用处......

 

posted @ 2021-11-23 13:50  闲人鹤  阅读(227)  评论(0编辑  收藏  举报