【Spring-Security】Re05 权限控制及403处理

一、访问控制方法及控制项:

上述配置中的URL后面都离不开的一个访问控制抉择:

1、全部允许                PermiAll 
2、全部拒绝                DenyAll
3、允许匿名访问             Anonymous 也就是普通访问者
4、允许认证之后访问        Authenticated
5、必须完全认证?           FullAuthenticated
6、记住我                  RememberMe

二、Security权限控制对资源的访问:

之前的权限配置中我们有设置一个权限的角色属性:

package cn.zeal4j.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 Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:57
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 1、通过提供的用户名参数访问数据库,查询记录返回过来,如果记录不存在则抛出异常
        // username = "admin";
        if (!"admin".equals(username)) throw new UsernameNotFoundException("用户名不存在");

        // 2、查询出来的凭证是被加密了的,这里是模拟查询的密码
        String encode = passwordEncoder.encode("123456");

        // 权限不可以为空,所以需要这么一个工具方法简单实现
        return new User(username, encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

现在我们设置两个特定的资源页面:

页面的内容区别就是这个:

<h3>Only admin Allowed</h3>
<h3>Only vip-01 Allowed</h3>

控制器处理:

package cn.zeal4j.controller;

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

/**
 * @author Administrator
 * @file Spring-Security-Tutorial
 * @create 2020 09 28 13:59
 */
@Controller
public class PermitController {
    
    @RequestMapping("admin.page")
    public String toAdminPage() {
        return "permit/admin";
    }

    @RequestMapping("vip-01.page")
    public String toVip01Page() {
        return "permit/vip-01";
    }
}

现在设置访问权限:

通过hasAuthority方法进行鉴权,具备资质才会允许访问

package cn.zeal4j.configuration;

import cn.zeal4j.handler.FarsAuthenticationFailureHandler;
import cn.zeal4j.handler.FarsAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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 Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆
                // 登陆请求参数设置
                usernameParameter("username").
                passwordParameter("password").
                loginPage("/login.html"). // 设置登陆页面URL路径
                loginProcessingUrl("/login.action"). // 设置表单提交URL路径
                // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求
                successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆
                // failureForwardUrl("/error.page");  // 设置认证失败跳转URL路径 POST请求
                failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了

        httpSecurity.authorizeRequests().
                regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式
                antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行
                antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问
                antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问
                antMatchers("/admin.page").hasAnyAuthority("admin").
                antMatchers("/vip-01.page").hasAnyAuthority("vip-01").
                // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配
                // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀
                anyRequest().authenticated(); // 其他请求均需要被授权访问

        // CSRF攻击拦截关闭
        httpSecurity.csrf().disable();
    }
}

访问测试:

我们默认登录之后赋予的权限中包含admin,所以访问admin.page是没有问题的

但是如果访问vip-01.page,则会报403错误,访问被禁止了

三、角色控制:

上述的权限设置中可以同时设置角色,书写规范是:

ROLE_角色名称

例如这样:

package cn.zeal4j.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 Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:57
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 1、通过提供的用户名参数访问数据库,查询记录返回过来,如果记录不存在则抛出异常
        // username = "admin";
        if (!"admin".equals(username)) throw new UsernameNotFoundException("用户名不存在");

        // 2、查询出来的凭证是被加密了的,这里是模拟查询的密码
        String encode = passwordEncoder.encode("123456");

        // 权限不可以为空,所以需要这么一个工具方法简单实现
        return new User(username, encode, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_vip-01"));
    }
}

之前访问不了的VIP-01资源,现在权限控制规则可以这样设置:

package cn.zeal4j.configuration;

import cn.zeal4j.handler.FarsAuthenticationFailureHandler;
import cn.zeal4j.handler.FarsAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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 Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆
                // 登陆请求参数设置
                usernameParameter("username").
                passwordParameter("password").
                loginPage("/login.html"). // 设置登陆页面URL路径
                loginProcessingUrl("/login.action"). // 设置表单提交URL路径
                // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求
                successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆
                // failureForwardUrl("/error.page");  // 设置认证失败跳转URL路径 POST请求
                failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了

        httpSecurity.authorizeRequests().
                regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式
                antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行
                antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问
                antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问
                antMatchers("/admin.page").hasAnyAuthority("admin").
                /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/
                antMatchers("/vip-01.page").hasRole("vip-01").
                // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配
                // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀
                anyRequest().authenticated(); // 其他请求均需要被授权访问

        // CSRF攻击拦截关闭
        httpSecurity.csrf().disable();
    }
}

重启项目运行:

现在VIP-01资源可以被正常的访问了:

案例演示只是使用了单一的角色和权限处理,但实际开发的环境下面更为复杂,Security对此也提供好了 多权限 + 多角色的API

四、IP地址判断:

在之前的JavaWeb的学习中,我们可以通过请求的HttpServletRequest对象中,获取发送的请求的客户端IP地址

注意在本机上的一些访问问题:

以不同的IP地址形式访问,服务端接受到的IP地址也是不一样的

1、第一种
http://localhost:8080/
打印结果 0:0:0:0:0:0:0:1

2、第二种
http://127.0.0.1:8080/
打印结果 127.0.0.1

3、第三种
http://192.168.43.180:8080/
打印结果 192.168.43.180

这里新建一个资源,仅限于第三种IP地址符合的控制

<h3>Only 192.168.43.180 IP-Host Allowed</h3>

然后配置控制器和权限:

@RequestMapping("ip.page")
public String toIpPage() {
    return "permit/ip";
}

看看非远程IP地址是否有效

antMatchers("/ip.page").hasIpAddress("192.168.43.180").

可以看到,使用Localhost是不被允许访问的

同样的127.0.0.1地址也不允许

只有最后的远程IP地址才能被允许访问:

五、自定义403处理方案

除了这样的403响应,前后端分离的处理是发送响应状态信息完成的,所以Security允许我们自己设置一个禁止访问的结果的处理

package cn.zeal4j.handler;

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

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

/**
 * @author Administrator
 * @file Spring-Security-Tutorial
 * @create 2020 09 28 14:37
 */
@Component public class CustomAccessDeniedHandler 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 printWriter = httpServletResponse.getWriter(); // json数据 : { "status" : "error" , "message" : "权限不足,访问被禁止!!!" } printWriter.print("{ \"status\" : \"error\" , \"message\" : \"权限不足,访问被禁止!!!\" }"); printWriter.flush(); printWriter.close(); } }

然后在配置类中设置,403访问禁止的异常抛出:

package cn.zeal4j.configuration;

import cn.zeal4j.handler.CustomAccessDeniedHandler;
import cn.zeal4j.handler.FarsAuthenticationFailureHandler;
import cn.zeal4j.handler.FarsAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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;
import org.springframework.security.web.access.AccessDeniedHandler;

/**
 * @author Administrator
 * @file IntelliJ IDEA Spring-Security-Tutorial
 * @create 2020 09 27 21:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆
                // 登陆请求参数设置
                usernameParameter("username").
                passwordParameter("password").
                loginPage("/login.html"). // 设置登陆页面URL路径
                loginProcessingUrl("/login.action"). // 设置表单提交URL路径
                // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求
                successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆
                // failureForwardUrl("/error.page");  // 设置认证失败跳转URL路径 POST请求
                failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了

        httpSecurity.authorizeRequests().
                regexMatchers(HttpMethod.POST, "正则表达式").permitAll(). // 还可以对符合正则表达式的请求方式进行要求,这个属性使用来制定请求的方式
                antMatchers("/**/*.js", "/**/*.css", "/**/images/*.*").permitAll(). // 静态资源放行
                antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问
                antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问
                antMatchers("/admin.page").hasAnyAuthority("admin").
                /*antMatchers("/vip-01.page").hasAnyAuthority("vip-01").*/
                antMatchers("/vip-01.page").hasRole("vip-01").
                antMatchers("/ip.page").hasIpAddress("192.168.43.180").
                // mvcMatchers("/main.page").servletPath("/xxx").permitAll(). // mvcMatchers资源放行匹配
                // antMatchers("/xxx/main.page").permitAll(). // 或者多写MSP的前缀
                anyRequest().authenticated(); // 其他请求均需要被授权访问

        // CSRF攻击拦截关闭
        httpSecurity.csrf().disable();

        httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

    }
}

403访问测试:

 

posted @ 2020-09-28 14:51  emdzz  阅读(1691)  评论(0编辑  收藏  举报