SpringSecurity

SpringSecurity

官网 : https://spring.io/guides/topicals/spring-security-architecture/

  • 需要对 Spring IOC 和 AOP 有了解

Spring Security 安全框架,目的解决系统安全,不再需要手动处理每一个资源的访问控制

应用程序安全性可以归结为或多或少的两个独立问题:身份验证(您是谁)授权(您可以做什么?)

  • 身份认证

身份验证的主要策略界面是AuthenticationManager,它只有一种方法:

public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
}
  • 授权(访问控制)

创建demo 项目

  1. 创建一个包含 Spring Security 的 SpringBoot 工程
<dependencies>
        <!-- Spring Security 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--Spring Web 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>
  1. 在 resources/static 目录下创建 login.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="/login" method="post">
    用户名 : <input type="text" name="username"/><br/>
    用户密码 : <input type="password" name="password"/><br/>
    <input type="submit"value="登录">
</form>
</body>
</html>
  1. 登录成功,跳转到 success.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Success</title>
</head>
<body>
<p style="align-content: center"> 登录成功</p>
</body>
</html>
  1. 登录失败,跳转到 failure.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Error</title>
</head>
<body>
<p style="align-content: center"> 登录失败,<a href="/login.html">请重新登录</a></p>
</body>
</html>
  1. 编写 LoginController
package com.yuanhy.springsecuritydemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author yuanhy
 * @date 2020-12-13 12:23
 */
@Controller
public class LoginController {
    @RequestMapping("/login")
    public String login(){
        return "redirect:success.html";
    }
}

  1. 启动 demo
package com.yuanhy.springsecuritydemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityDemoApplication {

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

}

  1. 输入 :http://localhost:8080/login.html ,出现如下页面,这是Spring Security 自带默认

  1. 在控制台可以看到生成的密码 :Using generated security password: 43b54d71-3861-4d48-8301-47b691d4222a(每次启动都不一样) ,登录名为 user

    输入用户和密码后,跳到我们自定义的 login.html 页面

在不做任何配置的基础上,启动 Web 项目,访问我们的 login.html 资源时,首先会跳转到 Spring Security 内置的登录页面,使用 Spring Security 提供的 用户 user 和自动生成的密码登录成功才可以访问我们的资源

UserDetailsService

用户自定义登录逻辑

获取判断登录信息的主要方法,返回 UserDetails ,找不到返回异常 UsernameNotFoundException

UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

UserDetails :通过这些方法获取判断信息

Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();

User,UserDetails 的子类 ,主要是这个子类完成操作。

public class User implements UserDetails, CredentialsContainer

PasswordEncoder 密码解析器,推荐子类 BCryptPasswordEncoder 对密码进行加密匹配

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

public class BCryptPasswordEncoder implements PasswordEncoder

测试例子

@Test
    public void testBCryptPasswordEncoder(){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //加密密码
        String encode = bCryptPasswordEncoder.encode("123");
        System.out.println("加密后密码 : "+encode);
        //匹配密码
        boolean matches = bCryptPasswordEncoder.matches("123", encode);
        System.out.println("匹配是否成功 ;"+(matches?"成功":"失败"));
        Assertions.assertTrue(matches);

    }

自定义登录逻辑

需要使用到接口 UserDetailsService、PasswordEncoder 以及它们的子类

Spring Security 要求:在进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例,所以不能直接 new 一个对象,通过一个配置文件来注册 bean ,让 Spring 去管理

SecurityConfig

package com.yuanhy.springsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author yuanhy
 * @date 2020-12-13 18:12
 */
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

实现 UserDetailsService

package com.yuanhy.springsecuritydemo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author yuanhy
 * @date 2020-12-13 18:17
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名去数据库查询,不存在则报异常 UsernameNotFoundException,这里测试,固定一个值
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户不存在!");
        }
        //比较密码,从数据库获取的密码是注册时已经加密过,这里是 123456 ,
        String encode = passwordEncoder.encode("123456");
        return new User(username,encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

重新启动后,不会出现自动生成密码,然后输入 admin ,123456 就可以访问 http://localhost:8080/login.html

自定义登录页面

通过修改配置类,替换Spring Security 自带的登录页面

  1. 继承 WebSecurityConfigurerAdapter 重写 protected void configure(HttpSecurity http) 方法
package com.yuanhy.springsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author yuanhy
 * @date 2020-12-13 18:12
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html");
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

重启后,访问 http://localhost:8080/login.html ,直接访问我们自定义的 login.html ,不再是 Spring Security 内置的,但是,出现一个问题,所有的资源都可以访问,例如 http://localhost:8080/success.html 也可以直接访问,需要修改配置类,必须经过登录认证才可以访问,否则返回登录页

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置表单提交
        http.formLogin()
                //自定义登录页面
                .loginPage("/login.html");
        //授权
        http.authorizeRequests()
                //放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //所有请求都必须经过认证才可以访问,必须登录
                .anyRequest().authenticated();
    }

完整的配置如下

package com.yuanhy.springsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author yuanhy
 * @date 2020-12-13 18:12
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置表单提交
        http.formLogin()
                //自定义登录页面
                .loginPage("/login.html")
                //必须和表单 Form 的 action url 对应的接口一致
                .loginProcessingUrl("/login")
                //登录成功后跳转的 servlet uri
                .successForwardUrl("/toSuccess");
        //关闭 csrf 防护
        http.csrf().disable();
        //授权
        http.authorizeRequests()
                //放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //所有请求都必须经过认证才可以访问,必须登录
                .anyRequest().authenticated();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

增加登录成功跳转servlet

@RequestMapping("/toSuccess")
    public String toSuccess(){
        return "redirect:success.html";
    }

登录失败后跳转配置,增加 .failureForwardUrl("/toFailure");

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置表单提交
        http.formLogin()
                //自定义登录页面
                .loginPage("/login.html")
                //必须和表单 Form 的 action url 对应的接口一致
                .loginProcessingUrl("/login")
                //登录成功后跳转的 servlet uri
                .successForwardUrl("/toSuccess")
                .failureForwardUrl("/toFailure");
        //关闭 csrf 防护
        http.csrf().disable();
        //授权
        http.authorizeRequests()
            	//登录失败放行页面
                .antMatchers("/failure.html").permitAll()
                //放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //所有请求都必须经过认证才可以访问,必须登录
                .anyRequest().authenticated();
    }

失败 servlet

@RequestMapping("/toFailure")
    public String toFailure(){
        return "redirect:failure.html";
    }

自定义登录用户名和密码

Spring Security 默认 username 、password

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="/login" method="post">
    用户名 : <input type="text" name="username"/><br/>
    用户密码 : <input type="password" name="password"/><br/>
    <input type="submit"value="登录">
</form>
</body>
</html>

当把 name="username" 、name="password" 的 username、password 改变时会登录失败

这里涉及到 UsernamePasswordAuthenticationFilter 过滤器

官方源码 :参数默认 username 、password 、postOnly=true ,表单提交只能是 Post

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;

可以自定义设置表单参数

配置参数

//配置自定义 username、password 参数
.usernameParameter("userName")
.passwordParameter("passWord")
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置表单提交
        http.formLogin()
                //配置自定义 username、password 参数
                .usernameParameter("userName")
                .passwordParameter("passWord")
                //自定义登录页面
                .loginPage("/login.html")
                //必须和表单 Form 的 action url 对应的接口一致
                .loginProcessingUrl("/login")
                //登录成功后跳转的 servlet uri
                .successForwardUrl("/toSuccess")
                .failureForwardUrl("/toFailure");
        //关闭 csrf 防护
        http.csrf().disable();
        //授权
        http.authorizeRequests()
                //登录失败放行页面
                .antMatchers("/failure.html").permitAll()
                //放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //所有请求都必须经过认证才可以访问,必须登录
                .anyRequest().authenticated();
    }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<form action="/login" method="post">
    用户名 : <input type="text" name="userName"/><br/>
    用户密码 : <input type="password" name="passWord"/><br/>
    <input type="submit"value="登录">
</form>
</body>
</html>

重启后也是可以访问的

自定义跳转到外网页面

以前端控制跳转到百度为例

设置登录成功跳转 :.successForwardUrl("/toSuccess")

进入 successForwardUrl 源码,发现

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

ForwardAuthenticationSuccessHandler 实现了 AuthenticationSuccessHandler 接口 ,做了一个 forward 转发

public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private final String forwardUrl;

    public ForwardAuthenticationSuccessHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> {
            return "'" + forwardUrl + "' is not a valid forward URL";
        });
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getRequestDispatcher(this.forwardUrl).forward(request, response);
    }
}

同样,我们也可以自定义一个 SuccessHandler 实现 AuthenticationSuccessHandler 接口,定义 url,添加构造器,重写 onAuthenticationSuccess 方法,调用重定向 :httpServletResponse.sendRedirect(url);

package com.yuanhy.springsecuritydemo.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author yuanhy
 * @date 2020-12-14 22:32
 */
public class SuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public SuccessHandler(String url) {
        this.url =url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
        //Authentication 认证信息
        User user = (User) authentication.getPrincipal();
        System.out.println("username : "+user.getUsername());
        System.out.println("password : "+user.getPassword());
        System.out.println("username : "+user.getAuthorities().toString());
        httpServletResponse.sendRedirect(url);
    }
}


然后修改配置类 SecurityConfig 中的 .successForwardUrl("/toSuccess") 为以下配置即可

.successHandler(new SuccessHandler("http://www.baidu.com"))

自定义登录失败跳转错误页面

登陆失败自定义跳转

设置登录失败跳转 :.failureForwardUrl("/toFailure");

进入 failureForwardUrl 源码,发现

/**
	 * Forward Authentication Failure Handler
	 * @param forwardUrl the target URL in case of failure
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
		failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
		return this;
	}

ForwardAuthenticationFailureHandler实现了 AuthenticationFailureHandler 接口 ,做了一个 forward 转发

public class ForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {

	private final String forwardUrl;

	/**
	 * @param forwardUrl
	 */
	public ForwardAuthenticationFailureHandler(String forwardUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> "'" + forwardUrl + "' is not a valid forward URL");
		this.forwardUrl = forwardUrl;
	}

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
		request.getRequestDispatcher(this.forwardUrl).forward(request, response);
	}

同样,我们也可以自定义一个 FailureHandler 实现 AuthenticationFailureHandler接口,定义 url,添加构造器,重写 onAuthenticationFailure方法,调用重定向 :response.sendRedirect(url);

package com.yuanhy.springsecuritydemo.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author yuanhy
 * @date 2020-12-14 22:55
 */
public class FailureHandler implements AuthenticationFailureHandler {
    private final String url;

    public FailureHandler(String url) {
        this.url = url;
    }
	//AuthenticationException 重点关注异常子类
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

然后修改配置类 SecurityConfig 中的 .failureForwardUrl("/toFailure") 为以下配置即可

//自定义登录失败后跳转的页面
.failureHandler(new FailureHandler("failure.html"));

授权

权限控制

anyRequest().authenticated() 必须放在 antMatchers 后面,按顺序从上往下执行

//授权
        http.authorizeRequests()
                //登录失败放行页面
                .antMatchers("/failure.html").permitAll()
                //放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //所有请求都必须经过认证才可以访问,必须登录
                .anyRequest().authenticated();

静态资源放行

例如 图片 .jpg 、login.html、.css、.js 等

antMatchers 匹配规则

? 匹配一个字符

* 匹配 0 个或多个字符

** 匹配 0 个或多个目录

/**
	 * Maps a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher}
	 * instances that do not care which {@link HttpMethod} is used.
	 * @param antPatterns the ant patterns to create
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C antMatchers(String... antPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
	}

在实际项目中经常需要放行静态资源,所以演示放行静态资源文件下的资源

//放行静态资源,放行css、js、images 文件下的所有资源
.antMatchers("/css/**","/js/**","/images/**").permitAll()

把所有的图片放行,但是图片不一定都在 images 文件夹 :.antMatchers("/**/*.jpg").permitAll()

//放行资源 login.html
                .antMatchers("/login.html").permitAll()
                //放行静态资源,放行 css、js、images 文件下的所有资源
                //.antMatchers("/css/**","/js/**","/images/**").permitAll()
                //放行后缀为 .jpg 的所有图片
                //.antMatchers("/**/*.jpg").permitAll()
                //正则表达式匹配
                .regexMatchers(".+[.]jpg").permitAll()

配置 ServletPath

spring.mvc.servlet.path=/demo

在 application.properties 配置 spring.mvc.servlet.path=/demo,在配置类中使用 mvcMatchers

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

permitAll 允许所有

permitAll、denyAll(禁止所有)、anonymous(匿名)、authenticated(默认登录认证)、fullyAuthenticated(完全认证,用户名和密码认证)、rememberMe(免登录,记住我)

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {

	static final String permitAll = "permitAll";

	private static final String denyAll = "denyAll";

	private static final String anonymous = "anonymous";

	private static final String authenticated = "authenticated";

	private static final String fullyAuthenticated = "fullyAuthenticated";

	private static final String rememberMe = "rememberMe";

权限控制

删除 ServletPath 配置

增加 main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>权限控制</p>
</body>
</html>

在 login.html 里增加跳转到 main.html 链接

<p style="align-content: center"> 登录成功,请跳转<a href="main.html">权限控制</a></p>

配置权限,如果登录用户不匹配 admin ,则报 403 无权限

http.authorizeRequests()
                //权限控制,严格大小写
                .antMatchers("/main.html").hasAuthority("admin")

可以配置多权限

.antMatchers("/main.html").hasAnyAuthority("admin","Admin")

角色控制

角色以 ROLE_ 开头匹配

在登录逻辑增加角色

ROLE_root :AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_root")

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(username);
        //根据用户名去数据库查询,不存在则报异常 UsernameNotFoundException,这里测试,固定一个值
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户不存在!");
        }
        //比较密码,从数据库获取的密码是注册时已经加密过,这里是 123456 ,
        String encode = passwordEncoder.encode("123456");
        return new User(username,encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_root"));
    }
//角色控制,这里不需要 ROLE_ 严格区分大小写
.antMatchers("/main.html").hasRole("root")
//多角色
.antMatchers("/main.html").hasAnyRole("root","temp")
//基于 ip 控制
.antMatchers("/main.html").hasIpAddress("127.0.0.1")

自定义无权限 403 错误提示

创建类 MyAccessDeniedHandler 实现 AccessDeniedHandler,重写 handle 方法

package com.yuanhy.springsecuritydemo.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author yuanhy
 * @date 2020-12-15 22:42
 */
@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.setHeader("Content-Type","application/json;charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        printWriter.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
        printWriter.flush();
        printWriter.close();
    }
}

在 SecurityConfig 配置

        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

自定义 access

创建接口 MyService ,创建实现类 MyServiceImpl

package com.yuanhy.springsecuritydemo.service;

import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;

/**
 * @author yuanhy
 * @date 2020-12-15 23:26
 */
public interface MyService {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication);
}

package com.yuanhy.springsecuritydemo.service;

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

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

/**
 * @author yuanhy
 * @date 2020-12-15 23:28
 */
@Service
public class MyServiceImpl implements MyService {
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //获取主体
        Object principal = authentication.getPrincipal();
        //判断是否属于 UserDetails
        if(principal instanceof UserDetails){
            UserDetails userDetails = (UserDetails) principal;
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }
        return false;
    }
}

在 SecurityConfig 配置

.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");

配置 uri /success.html

return new User(username,encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_root,ROLE_temp,/success.html"));

基于注解的访问控制

默认不可用 ,需要通过在启动类配置 @EnableGlobalMethodSecurity 开启后使用

通常写在 Service 接口或方法上,也可以写在 Controller 或 Controller 的方法上,一般是 Controller,控制接口 URL 是否允许被访问

@Secured

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

posted @ 2021-03-08 22:42  袁胡悦  阅读(110)  评论(0编辑  收藏  举报