SpringBoot整合SpringSecurity(五)权限控制

Spring Security具有强大的权限验证。

权限有些人认为是页面的隐藏,其实不然。权限可以理解为是否可以访问资源,页面隐藏什么的是客户友好度的事情,所以对于web而言,系统的安全不安全,最终取决于对url的控制。

本文章代码可以参考 https://gitee.com/Maoxs/security-test中的 security-permission

准备

页面

首先呢是登陆


<html>
<head>
    <meta charset="UTF-8">
    <title>登录title>
head>
<body>
<h3>表单登录h3>
<table>
    <tr>
        <td>用户名:td>
        <td><input type="text" name="username">td>
    tr>
    <tr>
        <td>密码:td>
        <td><input type="password" name="password">td>
    tr>
    <tr>
        <td colspan="2">
            <button type="button" onclick="login()">登录button>
        td>
    tr>
table>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">script>
<script>
    function login() {
        var username = $("input[name=username]").val();
        var password = $("input[name=password]").val();
        if (username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/authentication/form",
            data: {
                "username": username,
                "password": password
            },
            success: function (e) {
                console.log(e);
                alert("登陆成功")
                setTimeout(function () {
                    location.href = '/hello';
                }, 1500);
            },
            error: function (e,a,b) {
                console.log(e.responseText);
                alert("登陆失败")
            }
        });
    }

script>
body>
html>

然后是我自己测试权限的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陆成功</h1>
<button onclick="check(this)" data-href="/java">检测java角色</button>
<button onclick="check(this)" data-href="/docker">检测docker角色</button>
<button onclick="check(this)" data-href="/php">检测php角色</button>
<button onclick="check(this)" data-href="/custom">检测自定义匹配器</button>
<a href="/logout">退出登录</a>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
    function check(e) {
        var url = e.dataset.href;
        $.ajax({
            type: "POST",
            url: url,
            success: function (e) {
                alert(e)
            },
            error: function (e, a, b) {
                console.log(e);
                alert("没有权限")
            }
        });
    }
</script>
</html>

数据

这里呢我们就不去具体的访问数据库了,准备一些模拟的数据,一个用户可以用多个角色和权限。

首先俩实体

@Data
public class SysRole implements Serializable {
    private Long id;
    private String roleName;
    public SysRole(Long id,String roleName){
    	this.id=id;
    	this.roleName=roleName;
    }
}
@Data
public class SysUser implements Serializable {

    private Long id;
    private String userName;
    private String password;
    private List<SysRole> roles;


    public SysUser(Long id, String userName, String password, List<SysRole> roles) {
        this.id = id;
        this.userName = userName;
        this.password = password;
        this.roles = roles;
    }

}
public class InitData {

    public static final Set<SysUser> SYS_USERS = new HashSet<>();

    public static final Set<SysRole> SYS_ROLES = new HashSet<>();

    static {
        SYS_ROLES.add(new SysRole(1L, "ROLE_JAVA");
        SYS_ROLES.add(new SysRole(2L, "ROLE_DOCKER");
        SYS_ROLES.add(new SysRole(3L, "ROLE_PHP");
        SYS_ROLES.add(new SysRole(4L, "ROLE_PYTHON");
        SYS_ROLES.add(new SysRole(5L, "ROLE_CENTOS");
    }

    static {
        SYS_USERS.add(
                new SysUser(1L, "fulin", "123456",
                        SYS_ROLES.stream().filter(o -> StringUtils.equalsAny(o.getRoleName(), "ROLE_JAVA", "ROLE_DOCKER")).collect(Collectors.toList())
                )
        );
        SYS_USERS.add(
                new SysUser(2L, "maoxiansheng", "123456",
                        SYS_ROLES.stream().filter(o -> StringUtils.equalsAny(o.getRoleName(), "ROLE_PHP", "ROLE_DOCKER")).collect(Collectors.toList())
                )
        );
        SYS_USERS.add(
                new SysUser(3L, "happy fish", "123456",
                        SYS_ROLES.stream().filter(o -> StringUtils.equalsAny(o.getRoleName(), "ROLE_PYTHON", "ROLE_CENTOS")).collect(Collectors.toList())
                )
        );
    }

}

然后是UserDetailsService

@Service
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser sysUser = InitData.SYS_USERS.stream().filter(o -> StringUtils.equals(o.getUserName(), s)).findFirst().orElse(null);
        if (sysUser == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //模拟从数据库获取角色权限
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        List<SysRole> roles = sysUser.getRoles();
        for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        return new User(sysUser.getUserName(), sysUser.getPassword(), authorities);
    }

}

controller

@Controller
@RequestMapping
public class TestController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
}

然后是需要鉴权的controller

@RestController
public class PermissionController {
    @RequestMapping("/docker")
    public String test1() {
        return "说明你有docker权限";
    }
    @RequestMapping("/custom")
    public String test0() {
        return "说明你有自定义权限";
    }
    @RequestMapping("/java")
    public String test2() {
        return "说明你有java权限";
    }
    @RequestMapping("/php")
    public String test3() {
        return "说明你有最好语言的权限";
    }
}

然后就进入重头戏了

权限配置

这一篇内我们主要讲在configure 中配置的形式,下一篇会说明比较强大的 权限注解。

先看配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final FailureAuthenticationHandler failureAuthenticationHandler;
    private final SuccessAuthenticationHandler successAuthenticationHandler;
    private final UserService userService;
    private final AccessDeniedAuthenticationHandler accessDeniedAuthenticationHandler;
   

    public WebSecurityConfig(UserService userService, FailureAuthenticationHandler failureAuthenticationHandler, SuccessAuthenticationHandler successAuthenticationHandler,AccessDeniedAuthenticationHandler accessDeniedAuthenticationHandler)   {
        this.userService = userService;
        this.failureAuthenticationHandler = failureAuthenticationHandler;
        this.successAuthenticationHandler = successAuthenticationHandler;
        this.accessDeniedAuthenticationHandler = accessDeniedAuthenticationHandler;
    }
    /**
     * 注入身份管理器bean
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 注入自定义权限管理
     *
     * @return
     * @throws Exception
     */
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return handler;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(
                new PasswordEncoder() {
                    @Override
                    public String encode(CharSequence charSequence) {
                        return charSequence.toString();
                    }

                    @Override
                    public boolean matches(CharSequence charSequence, String s) {
                        return s.equals(charSequence.toString());
                    }
                });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
                .successHandler(successAuthenticationHandler) // 自定义登录成功处理
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/authentication/form") // 自定义登录路径
                .and()
                .authorizeRequests()// 对请求授权
                .antMatchers("/login", "/authentication/require",
                        "/authentication/form").permitAll()// 这些页面不需要身份认证
                .antMatchers("/docker").hasRole("DOCKER")
                .antMatchers("/java").hasRole("JAVA")
                .antMatchers("/java").hasRole("JAVA")
                .antMatchers("/custom")
                .access("@testPermissionEvaluator.check(authentication)")
                .anyRequest()//其他请求需要认证
                .authenticated().and().exceptionHandling()
                .accessDeniedHandler(accessDeniedAuthenticationHandler)
                .and()
                .csrf().disable();// 禁用跨站攻击
    }

}

这里呢我们要说明一下

authorizeRequests() 就是请求授权,然后其中的antMatchers() 就是匹配对应的url。

我们看到后面的permitAll()hasRole("ROLE_DOCKER") 这些都可以叫做权限表达式。我总结了一些差不多有这么多

表达式 说明
hasRole([role]) 用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀)
hasAnyRole([role1,role2]) 用户拥有任意一个制定的角色时返回true
hasAuthority([authority]) 等同于hasRole,但不会带有ROLE_前缀
asAnyAuthority([auth1,auth2]) 等同于hasAnyRole
permitAll 永远返回true
denyAll 永远返回false
authentication 当前登录用户的authentication对象
fullAuthenticated 当前用户既不是anonymous也不是rememberMe用户时返回true
hasIpAddress('192.168.1.0/24')) 请求发送的IP匹配时返回true

自定义权限表达式

然后来就要说一下这个access() 了,spring3.0后出了spel 超好用,有了这个我们就可以设置自己的权限验证了

比如说可以组合操作access("hasRole('JAVA') or hasRole('DOCKER')")

我写的这句 access("@testPermissionEvaluator.check(authentication)") 的意思就是 去testPermissionEvaluator这个bean里来执行check方法,这里需要注意check 方法必须返回值是boolean的因为这个是要给投票器投票的,这个我们以后会说

来看看我这个bean的代码吧

interface TestPermissionEvaluator {
    boolean check(Authentication authentication);
}

@Service("testPermissionEvaluator")
public class TestPermissionEvaluatorImpl implements TestPermissionEvaluator {

    public boolean check(Authentication authentication) {
        //这里可以拿到登陆信息然后随便的去定制自己的权限 随便你怎么查询
        //true就是过,false就是不过
        System.out.println("进入了自定义的匹配器" + authentication);
        return false;
    }
}

这里要说一下spring security没有权限的时候默认返回的是页面。像我这样js掉用返回json就需要配置一下权限异常处理器了。只需要实现一个AccessDeniedHandler 的接口即可

@Component
@Slf4j
public class AccessDeniedAuthenticationHandler implements AccessDeniedHandler {
    private final ObjectMapper objectMapper;

    public AccessDeniedAuthenticationHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }


    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        log.info("没有权限");
        httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));
    }
}

然后在configure 中这样配置下就行了

.authenticated().and().exceptionHandling().accessDeniedHandler(accessDeniedAuthenticationHandler)

然后我们启动项目验证一下
SpringBoot整合SpringSecurity(五)权限控制_第1张图片

然后下一篇帖子我会说权限注解,那个是真的方便。

本博文是基于springboot2.x 和security 5 如果有什么不对的请在下方留言。

你可能感兴趣的:(SpringBoot整合SpringSecurity(五)权限控制)