Spring Security utiliza @PreAuthorize y @PostAuthorize para implementar la autenticación de autorización del sistema web

Spring Security puede usar @PreAuthorize y @PostAuthorize para el control de acceso, como se describe a continuación.
El proyecto utiliza Springboot2.3.3 + SpringSecurtiy + Mbatis para lograr.
Primero introduzca pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- 需要单独添加thymeleaf的布局模块 -->
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

El log4j2 se utiliza en el proyecto para reemplazar el sistema de registro de logback, y es necesario configurar el archivo de configuración de log4j2.

El primer paso: realizar la configuración de SpringSecurity

Amplíe la clase de configuración de WebSecurityConfigurerAdapter

/**
 * @author MaLei
 * @description: 新建一个WebSecurityConfig类,使其继 承WebSecurityConfigurerAdapter
 * 在给WebSecutiryConfig类中加上@EnableWebSecurity 注解后,便会自动被 Spring发现并注册(查看
 * @EnableWebSecurity 即可看到@Configuration 注解已经存在
 * @create 2020/7/14
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启全局方法配置这个注解必须开启否则@PreAuthorize等注解不生效
public class WebSecutiryConfig extends WebSecurityConfigurerAdapter {
    
    
    //认证管理器配置方法可以配置定定义的UserDetailService和passwordEncoder。无需配置springboot2.3会自动注入bean
   /* @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(UserDetailService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }*/

    //核心过滤器配置方法
    //void configure(WebSecurity web)用来配置 WebSecurity。而 WebSecurity是基于 Servlet Filter用来配置 springSecurityFilterChain。而 springSecurityFilterChain又被委托给了 Spring Security 核心过滤器 Bean DelegatingFilterProxy。  相关逻辑你可以在 WebSecurityConfiguration中找到。一般不会过多来自定义 WebSecurity, 使用较多的使其ignoring()方法用来忽略Spring Security对静态资源的控制.对于静态资源的忽略尽量在此处设置,否则容易无限循环重新定向到登录页面
    @Override
    public void configure(WebSecurity web) throws Exception {
    
    
        web.ignoring().antMatchers("/static/**", "/mylogin.html","/admin", "/favicon.ico");
}

    //安全过滤器链配置方法
    //void configure(HttpSecurity http)这个是我们使用最多的,用来配置 HttpSecurity。 HttpSecurity用于构建一个安全过滤器链 SecurityFilterChain。SecurityFilterChain最终被注入核心过滤器 。 HttpSecurity有许多我们需要的配置。我们可以通过它来进行自定义安全访问策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
       // super.configure(http); 不能使用默认的验证方式
        //authorizeRequests()方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的
        //anyanyRequest()、antMatchers()和regexMatchers()等方法来匹配系统的URL,并为其指定安全
        //策略
       http.authorizeRequests()
                .anyRequest().authenticated() 
                .and()
                //formLogin()方法和httpBasic()方法都声明了需要Spring Security提供的表单认证方式,分别返
                //回对应的配置器
                .formLogin()
                //,formLogin().loginPage("/myLogin.html")指定自定义的登录
                ///myLogin.html,同时,Spring Security会用/myLogin.html注册一个POST路由,用于接收登录请求
               //loginProcessingUrl("/login")指定的/login必须与表单提交中指向的action一致
                   .loginPage("/mylogin.html").loginProcessingUrl("/logins").permitAll()
               //表单中用户名和密码对应参数设置(默认为username和password),如果是默认值则不用设置下面的参数对应.
               .usernameParameter("usernames").passwordParameter("passwords")
               .successForwardUrl("/hello")
               .failureHandler(new AuthenticationFailureHandler() {
    
    
                   @Override
                   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    
    
                       httpServletResponse.setContentType("application/json;charset=UTF-8");
                       httpServletResponse.setStatus(403);
                       String error=new String();
                       if (e instanceof BadCredentialsException ||
                               e instanceof UsernameNotFoundException) {
    
    
                           error="账户名或者密码输入错误!";
                       } else if (e instanceof LockedException) {
    
    
                           error="账户被锁定,请联系管理员!";
                       } else if (e instanceof CredentialsExpiredException) {
    
    
                           error="密码过期,请联系管理员!";
                       } else if (e instanceof AccountExpiredException) {
    
    
                           error="账户过期,请联系管理员!";
                       } else if (e instanceof DisabledException) {
    
    
                           error="账户被禁用,请联系管理员!";
                       } else {
    
    
                           error="登录失败!";
                       }
                       httpServletResponse.getWriter().write("{\"message\":\""+error+"\"}");
                   }
               })
               .and()
               .logout()
               .logoutUrl("/logout")
               .logoutSuccessHandler(new LogoutSuccessHandler() {
    
    
                   @Override
                   public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException {
    
    
                       resp.setContentType("application/json;charset=utf-8");

                      @Cleanup PrintWriter out = resp.getWriter();
                       out.write("{\"msg\":\"注销成功!\"}");
                       out.flush();
                      // out.close();
                   }
               })
               .permitAll()
                .and()
                //csrf()方法是Spring Security提供的跨站请求伪造防护功能,当我们继承WebSecurityConfigurer
                //Adapter时会默认开启csrf()方法
                .csrf().disable()
               //只有确实的访问失败才会进入AccessDeniedHandler,如果是未登陆或者会话超时等,不会触发AccessDeniedHandler,而是会直接跳转到登陆页面
       .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
    
    
           @Override
           public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    
    
               httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
               httpServletResponse.setContentType("application/json;charset=UTF-8");
               PrintWriter out = httpServletResponse.getWriter();
               out.write(new ObjectMapper().writeValueAsString("{\"message\":\"权限不足,请联系管理员!\"}"));
               out.flush();
               out.close();
           }
       });

    }
    /**
     * 增加密码加密器,一旦增加,在验证过程中security将使用密码加密器进行加密对比,数据库中如果存储明文密码,在
     * UserDetailsService接口实现方法中,先加密密码然后才能返回UserDetails
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
    
    
        //使用系统自带密码加密器也可以参考上一篇自己继承PasswordEncoder接口写编码器
        return new BCryptPasswordEncoder();
    }
}

En el archivo de configuración, los recursos estáticos, las páginas de inicio de sesión, etc. se pueden acceder sin una cuenta de inicio de sesión. Los puse en el public void configure(WebSecurity web) throws Exceptionmétodo.

Paso 2: implementar las interfaces UserDetails y UserDetailsService.

La clase User implementa la interfaz UserDetails

public class User implements Serializable, UserDetails {
    
    
    private Long id;

    private String username;

    private String password;

    private String name;

    private Boolean enabled;

    private List<Role> roles;

    public List<Role> getRoles() {
    
    
        return roles;
    }

    public void setRoles(List<Role> roles) {
    
    
        this.roles = roles;
    }

    private static final long serialVersionUID = 1L;

    public Long getId() {
    
    
        return id;
    }

    public void setId(Long id) {
    
    
        this.id = id;
    }

    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 enabled;
    }

    public void setUsername(String username) {
    
    
        this.username = username == null ? null : username.trim();
    }
    //将当前账户的所属角色进行配置
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        List<GrantedAuthority> list=new ArrayList<>();
        //将当前用户的配属角色填入集合
        Assert.notNull(roles,"角色集合为null");
        for (Role r:roles){
    
    
            list.add(new SimpleGrantedAuthority(r.getName()));
        }
        return list.size()>0?list:null;
    }

    public String getPassword() {
    
    
        return password;
    }

    public void setPassword(String password) {
    
    
        this.password = password == null ? null : password.trim();
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name == null ? null : name.trim();
    }

    public Boolean getEnabled() {
    
    
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
    
    
        this.enabled = enabled;
    }

    @Override
    public String toString() {
    
    
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", username=").append(username);
        sb.append(", password=").append(password);
        sb.append(", name=").append(name);
        sb.append(", enabled=").append(enabled);
        sb.append("]");
        return sb.toString();
    }
}

La clase UserDetailService implementa la interfaz UserDetailsService

/**
 * @author MaLei
 * @description: UserDetailService
 * @create 2020/7/14
 */
@Component
@Slf4j
public class UserDetailService implements UserDetailsService {
    
    
    @Autowired
    UserMapper customerMapper;
    @Override
    @Transactional("firstTransactionManager")

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        log.info("登录账号:{}",username);
        //数据库读取账户,如果读出来的密码是明码必须使用PasswordEncoder加密
        User cust=customerMapper.selectByUserNameContainsRoles(username);
        if(cust==null)
            throw new UsernameNotFoundException("账户不存在");
        return cust;
    }
}

Debido a la operación de base de datos implementada por Mybatis, la implementación del mapeador específico no se publicará.

El tercer paso es usar @PreAuthorize y @PostAuthorize en el método anterior de Controller.
@PreAuthorize es realizar la autenticación de permisos
antes de que se ejecute el método @PostAuthorize es realizar la autenticación de permisos antes de regresar después de que se ejecute el método

@RestController
public class TestController {
    
    
    @RequestMapping("/hello")
    @PreAuthorize("hasPermission('/hello', 'read') or hasRole('ROLE_admin')")
    public String hello() {
    
    
        return  "hello";
}

El código @PreAuthorize ("hasPermission ('/ hello', 'read') o hasRole ('ROLE_admin')") también usa hasRole ('ROLE_admin'), lo que significa que el usuario que ha iniciado sesión actualmente puede acceder siempre que tiene el rol de ROLE_admin. El nombre del rol en hasRole ('ROLE_admin') puede escribirse como ROLE_admin o abreviarse como admin. El sistema determinará automáticamente si hay un prefijo ROLE_, y si no lo está, se agregará automáticamente.
El sistema hasPermission ('/ hello', 'read') no lo procesará automáticamente y necesita implementar la interfaz PermissionEvaluator para su procesamiento.

El cuarto paso: implementar la interfaz PermissionEvaluator

/**
 * @author MaLei
 * @description: PermissionEvaluator接口实现类
 * @create 2020/7/17
 */
@Configuration
public class MyPermissionEvaluator implements PermissionEvaluator {
    
    
    @Autowired
    MenuMapper menuMapper;
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
    
    
        boolean accessable = false;
        if(authentication.getPrincipal().toString().compareToIgnoreCase("anonymousUser") != 0){
    
    
            Menu menu= menuMapper.selectByRequestUrl(targetDomainObject.toString());
            if(menu==null) return accessable;
            List<Role> roles = menu.getRoles();
            Iterator<Role> it=roles.iterator();
            while(it.hasNext()) {
    
    
                Role role=it.next();
                for (GrantedAuthority authority : authentication.getAuthorities()) {
    
    
                   if(role.getName().equals(authority.getAuthority())){
    
    
                       accessable=true;
                   }
                }
            }
        }
        return accessable;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
    
    
        return false;
    }
}

public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission)El método es procesar
@PreAuthorize ("hasPermission ('/ hello', 'read') o hasRole ('ROLE_admin')")
la información pasada en la anotación hasPermission ('/ hello', 'read')
, Autenticación en el método hasPermission El parámetro de autenticación representa el usuario registrado y la información de permisos
Object targetDomainObject parámetro representa el primer parámetro "/ hello" en
hasPermission ('/ hello', 'read') El parámetro de permiso de objeto representa el segundo parámetro en hasPermission ('/ hola ',' leer ') Parámetro "leer"

En la versión alta de springboot, después de implementar la interfaz PermissionEvaluator, siempre que la clase de implementación se agregue al sistema @Configuration, el sistema se registrará automáticamente en el contenedor.

Si en la versión inferior de springboot, la clase de implementación personalizada PermissionEvaluator no entra en vigencia, debe declarar un @Bean en su propia implementación de la clase de configuración WebSecutiryConfig, en el siguiente formulario:

/**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
    
    
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        //将自己实现的PermissionEvaluator接口实现类加入处理器
        handler.setPermissionEvaluator(new MyPermissionEvaluator());
        return handler;
    }

A través de la configuración anterior, se realiza el uso de @PreAuthorize, @PostAuthorize y otras anotaciones para restringir los permisos. En este ejemplo, todas las solicitudes deben estar registradas para acceder. Si una cuenta está registrada, pero después de que se solicita un determinado recurso, el método de recurso no está disponible. Agregue la anotación @PreAuthorize o @PostAuthorize, significa que el recurso no necesita permiso de acceso y se puede acceder directamente. Preste atención a este punto.

Supongo que te gusta

Origin blog.csdn.net/u011930054/article/details/107411932
Recomendado
Clasificación