SpringBoot整合SpringSecurity(五)权限控制

序言

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

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

代码请参考 https://github.com/AutismSuperman/springsecurity-example

准备

页面

首先呢是登陆

<!DOCTYPE html>
<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)

然后我们启动项目验证一下
在这里插入图片描述

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

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

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot和Spring Security是一对好朋友,Spring Boot提供了强大的自动配置和快速开发的能力,而Spring Security则提供了完整的安全解决方案,可以实现用户认证、授权、安全过滤等功能。本文将介绍如何在Spring Boot中整合Spring Security实现权限控制。 1. 添加Spring Security依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 配置Spring Security 在Spring Boot中,可以通过application.properties或application.yml文件配置Spring Security。以下是一个简单的配置: ``` spring.security.user.name=admin spring.security.user.password=123456 spring.security.user.roles=ADMIN ``` 这个配置定义了一个用户名为admin,密码为123456,角色为ADMIN的用户。在实际应用中,应该将用户名和密码存储在数据库或其他安全存储中。 3. 创建SecurityConfig类 创建一个继承自WebSecurityConfigurerAdapter的SecurityConfig类,并重写configure方法: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin").password("{noop}123456").roles("ADMIN"); } } ``` configure方法定义了应用程序的安全策略,这里配置了所有请求都需要认证(即登录)才能访问,除了首页和登录页,这两个页面可以匿名访问。formLogin方法配置了自定义的登录页面,logout方法配置了退出登录的操作。 configureGlobal方法定义了一个内存中的用户,用户名为admin,密码为123456,角色为ADMIN。在实际应用中,应该将用户信息存储在数据库或其他安全存储中。 4. 创建登录页面 在templates目录下创建一个名为login.html的登录页面,例如: ``` <!DOCTYPE html> <html> <head> <title>Login Page</title> </head> <body> <h1>Login Page</h1> <div th:if="${param.error}"> Invalid username and password. </div> <div th:if="${param.logout}"> You have been logged out. </div> <form th:action="@{/login}" method="post"> <div> <label>Username:</label> <input type="text" name="username" /> </div> <div> <label>Password:</label> <input type="password" name="password" /> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html> ``` 5. 运行应用程序 在浏览器中访问http://localhost:8080/login,输入用户名admin和密码123456,即可登录成功。如果输入错误的用户名或密码,则会提示“Invalid username and password.”。如果成功登录后再访问http://localhost:8080/home,则可以看到“Welcome home!”的欢迎消息。 6. 实现权限控制 上面的例子中只实现了登录认证,没有实现权限控制。下面介绍如何实现权限控制。 首先需要在configureGlobal方法中添加更多的用户和角色: ``` @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin").password("{noop}123456").roles("ADMIN") .and() .withUser("user").password("{noop}password").roles("USER"); } ``` 这里定义了一个管理员用户和一个普通用户,分别拥有ADMIN和USER两个角色。 然后在configure方法中添加更多的安全策略: ``` @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } ``` 这里添加了一个安全策略,即/admin/**路径需要拥有ADMIN角色才能访问。 现在管理员用户可以访问/admin/**路径,而普通用户则不能访问。如果普通用户尝试访问/admin/**路径,则会提示“Access is denied”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值