[学习笔记] SpringSecurity之SpringSecurity入门(二)

# 学习 # · 2021-10-22

访问控制URL匹配

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

.anyRequest().authenticated()

2、antMatchers(String... antPatterns):放行的请求。

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

// static目录下的请求都会被放行
.antMatchers("/static/**").permitAll();
// 所有的.js文件请求都会被放行
.antMatchers("/**/*.js").permitAll();

3、regexMatchers(String... regexPatterns):使用正则表达式进行匹配。

// 放行所有的.js文件
.regexMatchers( ".+[.]js").permitAll()

// 只放行POST请求的toLogin
.regexMatchers(HttpMethod.POST, "/toLogin").permitAll()

4、mvcMatchers():适用于配置了servletPath(spring.mvc.servlet.path)的情况。

// 放行/admin/toLogin请求
.mvcMatchers("/toLogin").servletPath("/admin").permitAll()

内置访问控制方法

1、permitAll():表示所匹配的URL任何人都允许访问。

2、authenticated():表示所匹配的 URL 都需要被认证才能访问。

3、anonymous():表示可以匿名访问匹配的URL。

4、denyAll():表示所匹配的URL都不允许被访问。

5、rememberMe():表示被“remember me”的用户允许访问。

6、fullyAuthenticated():表示不被“remember me”的用户允许访问。


角色权限判断

1、角色权限判断:用于用 户已经被认证后,判断用户是否具有特定的要求。

2、hasAuthority():判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建User对象时指定的。

// 设置success请求只允许拥有main权限的用户访问
.antMatchers("/success").hasAuthority("main")
// 在UserDetailsService创建User对象时指定权限
User user = new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("normal,admin,main"));

return user;

3、hasAnyAuthority():如果用户具备给定权限中某一个,就允许访问。

// 设置success请求只允许拥有main或admin权限的用户访问
.antMatchers("/success").hasAnyAuthority("main","admin")

4、hasRole():如果用户具备给定角色就允许访问。

// 设置success请求只允许admin角色访问
.antMatchers("/success").hasRole("admin")
// 在UserDetailsService创建User对象时指定角色
// 格式:ROLE_角色名
User user = new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("normal,ROLE_admin,main"));

return user;

5、hasAnyRole():如果用户具备给定角色的任意一个,就允许访问。

// 设置success请求只允许main角色或admin角色权限的用户访问
.antMatchers("/success").hasAnyRole("main", "admin")

6、hasIpAddress():如果请求是指定的IP,就允许访问。

// 设置success请求只允许IP为127.0.0.1的客户端访问
.antMatchers("/success").hasIpAddress("127.0.0.1")

自定义403处理方案

1、方法1:基于Thymeleaf模板引擎,在templates模板目录下创建error文件夹,并在error文件夹下新建403.html自定义错误页面。

<body>
    权限不足
</body>

2、方法2:基于SpringSecurity:

(1)新建 AccessDeniedHandler自定义处理类:

/**
 * @Package: com.duo.handler
 * @Description: 自定义403处理
 * @Author 多仔
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("权限不足!");
        out.flush();
        out.close();
    }
}

(2)修改SpringSecurity配置类:

http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());

基于表达式的访问控制

1、基于access()方法的访问控制:

// 等同于.permitAll()
.access("permitAll")

// 等同于.hasRole("admin")
.access("hasRole('admin')")

2、自定义access方法:

(1)新建自定义access接口:

/**
 * @Package: com.duo.service
 * @Description: 自定义access接口
 * @Author 多仔
 */
public interface MyService {
    boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

(2)新建自定义access接口实现类:

/**
 * @Package: com.duo.service
 * @Description: 自定义access接口实现类
 * @Author 多仔
 */
@Service
public class MyServiceImpl implements MyService{
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        // 获取当前登录的用户信息
        Object obj = authentication.getPrincipal();
        if(obj instanceof User) {
            UserDetails user = (User) obj;
            // 获取当前登录的用户的权限列表
            Collection<? extends GrantedAuthority> authorities = user.getAuthorities();

            // 判断当前访问的URI是否存在于权限列表中
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }

        return false;
    }
}

(3)修改SpringSectrity配置文件:

http.authorizeRequests()
    // toLogin不需要被认证
    .antMatchers("/toLogin").permitAll()
    // error不需要被认证
    .antMatchers("/error").permitAll()
    // 所有请求都必须被认证,必须登录后被访问
    .anyRequest().access("@myServiceImpl.hasPermission(request, authentication)");

(4)为用户添加权限:

User user = new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("normal,admin,/success"));

return user;

基于注解的访问控制

1、@Secured:用于判断是否具有角色权限,可写在方法或类上,参数要以 ROLE_开头。

(1)启动@Secured注解支持:

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringsecurityDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringsecurityDemoApplication.class, args);
    }

}

(2)在类上或方法上使用注解:

@Secured(value = "ROLE_main")
@GetMapping("success")
@ResponseBody
public String getSuccess() {
    return "登录成功";
}

2、@PreAuthorize:表示访问方法或类在执行之前先判断权限,注解 的参数和access()方法参数取值相同,都是权限表达式。

(1)开启@PreAuthorize注解支持:

@EnableGlobalMethodSecurity(prePostEnabled = true)

(2)在类上或方法上使用注解:

@PreAuthorize("hasRole('ROLE_main')")
@getMapping("/edit")
public String getEdit(){
   return "edit";
}

3、@PostAuthorize:表示方法或类执行结束后判断权限。


RememberMe的实现

1、Spring Security中的Remember Me功能,用户只需要在登录时添加remember-me复选框,取值为true。Spring Security会自动把用户信息存储到数据源中。

2、实现方法:

(1)确保项目已经连接数据库。

(2)修改SpringSecurity配置类,注入数据源和PersistentTokenRepository:

@Autowired
private DataSource dataSource;

@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);

    // 自动建表,首次启动时需要,重复启动时注释掉
    jdbcTokenRepository.setCreateTableOnStartup(true);

    return jdbcTokenRepository;
}

(3)修改SpringSecurity配置类,实现Remember Me:

http.rememberMe()
    // 失效时间,单位为秒
    .tokenValiditySeconds(120)
    // 设置自定义登录逻辑(@Autowired注入)
    .userDetailsService(userService)
    // 设置持久层对象
    .tokenRepository(persistentTokenRepository);

(4)前端添加Remember Me选框:

<input type="checkbox" name="remember-me" value="true"/>

(5)运行测试。


Thymeleaf整合SpringSecurity

1、添加Thymeleaf依赖:

<!-- Thymeleaf-SpringSecurity依赖 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!-- Thymeleaf依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2、在html页面中引入thymeleaf命名空间和security命名空间:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    登录成功!
</body>
</html>

3、整合获取属性:

登录账号:<span sec:authentication="name"></span><br/>
登录账号:<span sec:authentication="principal.username"></span><br/>
凭证:<span sec:authentication="credentials"></span><br/>
权限和角色:<span sec:authentication="authorities"></span><br/>
客户端地址:<span sec:authentication="details.remoteAddress"></span><br/>
sessionId:<span sec:authentication="details.sessionId"></span><br/>

4、整合权限判断:

通过权限判断:
<button sec:authorize="hasAuthority('/insert')">增</button>
<button sec:authorize="hasAuthority('/delete')">除</button>
<button sec:authorize="hasAuthority('/update')">改</button>
<button sec:authorize="hasAuthority('/select')">查</button>
<br/>
通过角色判断:
<button sec:authorize="hasRole('admin')">增</button>
<button sec:authorize="hasRole('admin')">除</button>
<button sec:authorize="hasRole('admin')">改</button>
<button sec:authorize="hasRole('admin')">查</button>

退出登录的实现

1、SpringSecurity默认的退出登录请求:默认的退出url为/logout,退出成功后跳转到/login? logout。

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

2、自定义退出登录请求的URL:

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

3、自定义退出成功处理器:实现LogoutSuccessHandler接口。

/**
 * @Package: com.duo.handler
 * @Description: 退出登录处理
 * @Author 多仔
 */
public class MyLogoutHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        super.onLogoutSuccess(request, response, authentication);

        System.out.println("用户退出登录");
    }
}

SpringSecurity的CSRF

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

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

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

4、Spring Security中的CSRF:

(1)从Spring Security4开始CSRF防护默认开启,默认会拦截请求,进行CSRF处理。

(2)CSRF为了保证不是其他第三方请求,要求访问时携带参数名为 _csrf、值为token(由服务端产生)的内容,如果前端传递的token和服务端生成的token匹配成功,则正常访问。

<!-- 前端添加__csrf表单内容 -->
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
// SpringSecurity启用CSRF防护,即删除掉以下代码
// http.csrf().disable();
如无特殊说明,本博所有文章均为博主原创。

如若转载,请注明出处:一木林多 - https://www.l5v.cn/archives/323/

评论