写点什么

Spring Security 的介绍和简单使用

  • 2023-04-14
    湖南
  • 本文字数:4313 字

    阅读完需:约 14 分钟

Spring Security 的介绍和简单使用

Spring Security 的介绍简介平时我们写 Web 项目,都需要用户登录时验证,以及权限管理之类的操作,以前使用过滤器,拦截器等进行管理,原生代码较多。


所以出现了安全框架以供我们使用,安全框架在 Web 应用的主要功能是:


认证授权使用的较多的安全框架有两个:shiro、Spring Security


Spring Security 是 Spring 家族中的一个安全管理框架,相比与另外一个安全框架 Shiro,它提供了更丰富的功能,社区资源也比 Shiro 丰富。


两个框架的主要功能相差不大,核心功能依旧是:认证、授权。


Spring Security 的几个重要类:


WebSecurityConfigurerAdapter:自定义 Sercurity 策略 AuthenticationManagerBuilfer:自定义认证策略 @EnableWebSecurity:开启 WebSecurity 模式 SpringSecurity 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。


快速使用使用 Spring Boot 集成 Spring Security 结合 Web 应用。


Web 应用准备:


三个等级,不同等级访问不同的区域。


在 maven 项目中导入 Spring Security 的依赖:


<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>复制代码查看 maven 项目的各个依赖版本:Spring Boot:2.2.1,Spring Security:5.2.1


开始使用 Spring Security 实现授权和认证效果:


// 自定义 Spring Security 的策略// 需要继承 WebSecurityConfigurerAdapter,作用是 --> 启用 Web 安全 @EnableWebSecurity // 该注解包含了 @Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {// 采用的是链式编程


// 授权// 请求授权的规则@Overrideprotected void configure(HttpSecurity http) throws Exception {    http.authorizeRequests()    // 授权请求,那些请求需要授权            .antMatchers("/").permitAll() // 规定首页所有人都能请求            .antMatchers("/level1/**").hasRole("vip1") // /level1/** 请求,只能 vip1 角色可以访问            .antMatchers("/level2/**").hasRole("vip2")            .antMatchers("/level3/**").hasRole("vip3");    // 没有权限会跳到登录页面:源码默认是 /login    http.formLogin();}
// 认证@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { // 添加用户至内存中,内存中身份验证
auth.inMemoryAuthentication() // 创建用户名为:zhangSan,密码为 123,角色是 vip1 和 vip2 .withUser("zhangSan").password("123").roles("vip1","vip2") .and() // and() 来衔接不同的用户。 // 创建用户名为:root,密码为 root,角色是 vip1、vip2、vip3 .withUser("root").password("root").roles("vip1","vip2","vip3") // 创建用户名为:user,密码为 123,角色是 vip1 .and() .withUser("user").password("123").roles("vip1"); // 使用 数据库中的数据进行身份验证 // auth.jdbcAuthentication();}
复制代码


}复制代码授权方法中 http.formLogin(); 源码的注释。


用户登录界面:Spring Security 默认的登录界面,如有需要可以手动修改路径,http.loginPage("url")


登录成功,但是服务器报错:


此时需要对认证部分的代码每个用户的密码进行加密处理。


// 认证 @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// auth.passwordEncoder() 指定加密方式。Security 5.0+ 提供了很多加密方式 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangSan").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2").and().withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1","vip2","vip3").and().withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");}复制代码登陆过后:就会按照上诉的定义的规则,Spring Security 帮我们进行授权和认证操作。


zhangSan:vip1、vip2root:vip1、vip2、vip3user:vip1 每个用户只能根据自己的角色请求授权的资源(请求)。


注销加入用户注销效果:


既然是注销,肯定是授权的一些操作了。


// 开启注销功能 http.logout().logoutSuccessUrl("/"); // 规定注销成功后,重定向到 /复制代码


上图标识了一些注意事项和例子,在前端只要发起/logout 请求即可将用户注销,在代码中还能清除 Cookie 或 Session 等后续操作。


<a href="/logout">注销</a>


权限控制通过上面的代码,一个简单的 Web 应用授权和认证就完成了,只是没有跟网页进行联动,可能不同的角色或用户,在同一个页面看到的菜单栏显示不一样,根据角色的权限不同看到的内容也不同。


所以接下来进行对角色进行权限控制。


如果需要网页根据登录用户的角色进行动态显示内容,则需要 Security 和 thymeleaf 的整合包。


整合包与 Spring Boot 的版本有关:


Spring Boot 2.1.0+ 版本,


<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency> 复制代码


html 页面引入


Spring Boot 2.0.9- 版本,


<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> <version>3.0.4.RELEASE</version> </dependency> 复制代码


html 页面引入


xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" 复制代码


此处 Spring Boot 的版本是 2.2.1,导入 thymeleaf-extras-springsecurity5 依赖,在前端页面进行标签设置。


<!-- 显示登录用户和角色 --><span sec:authentication="name"></span><span>,你好你的角色有:</span><span sec:authentication="principal.authorities"></span>
<div class="div1">
<!-- 如果当前用户角色是 vip1,才显示 --> <div class="div2" sec:authorize="hasRole('vip1')"> <h3>level 1</h3> <ul> <li><a th:href="@{level1/1}">level1-1</a></li> <li><a th:href="@{level1/2}">level1-2</a></li> <li><a th:href="@{level1/3}">level1-3</a></li> </ul> </div>
<!-- 如果当前用户角色是 vip2,才显示 --> <div class="div2" sec:authorize="hasRole('vip2')"> <h3>level 2</h3> <ul> <li><a th:href="@{level2/1}">level2-1</a></li> <li><a th:href="@{level2/2}">level2-2</a></li> <li><a th:href="@{level2/3}">level2-3</a></li> </ul> </div>
<!-- 如果当前用户角色是 vip3,才显示 --> <div class="div2" sec:authorize="hasRole('vip3')"> <h3>level 3</h3> <ul> <li><a th:href="@{level3/1}">level3-1</a></li> <li><a th:href="@{level3/2}">level3-2</a></li> <li><a th:href="@{level3/3}">level3-3</a></li> </ul> </div></div>
复制代码


</div>复制代码看到具体的整合包部分标签:


sec:authorize="isAuthenticated()"isAuthenticated 判断是否经过了身份验证,返回 boolean 值。sec:authentication="name"获取当前用户的用户名。sec:authentication="principal.authorities"获取当前用户的所有角色。sec:authorize="hasRole('role')"只有用户角色包括 role 才会显示。效果截图:


登录 zhangSang 用户:


登录 root 用户:


登录 user 用户:


到此,根据用户的角色动态显示内容功能简单实现。


自定义登录界面和开启 RememberMe 功能开启 RememberMe 功能。http.rememberMe();


点击登录就有:


这样就有了 remember me 功能,默认是 14 天时间。


自定义登录界面 login.html


<form th:action="@{/toLogin}" method="post"><input type="text" name="username" placeholder="用户名:"><input type="password" name="password" placeholder="密码:"><input type="checkbox" name="remember"> 记住我<input type="submit" value="登录"></form>


http.formLogin().loginPage("/toLogin");复制代码


登录成功。


这个项目中并没有实现对 toLogin 的登录验证实现,从头到尾都是 Spring Security 框架帮我们进行登录验证的处理,用户就是在内存中写的用户。所以我们修改了两处地方:


准备一个登录界面(login.html)和对于的 Controller (/toLogin),如果是 /login,则会被 Security 拦截直接走默认的 login 页面。修改登录页面的 url:http.formLogin().loginPage("/toLogin");修改登录界面的表单提交的 action:/toLogin 按照这个步骤结合我们以前写的用户验证过程,大概原理应该是 Security 根据 loginPage 指定的登录界面进行拦截处理,并且帮我们提取表单参数从而进行一系列操作。


关键是 loginPage 的 url 与 表单提交的 action 保持一致,这样 Security 才能够拦截成功,经过多次尝试,只要 Controller 中有映射,loginPage 就可以指定该映射,效果一样。


那么帮我们进行表单验证的到底是谁呢?如果没有其他设置的话,默认帮我们处理的还是之前的那个页面表单提交的 url。


这里就引出一个问题:自定义的表单提交的参数是不是需要固定?


默认情况下 Security 的表单验证的部分固定参数可以在源码中看到:


用户名:username 密码:password 记住我选项:remember 如果表单的 name 参数不一致,需要在 Security 配置中进行指定:


// 没有权限会跳到登录页面:源码默认是 /loginhttp.formLogin().loginPage("/to").loginProcessingUrl("/login").usernameParameter("user").passwordParameter("pwd");


// 开启 rememberMe 功能 http.rememberMe().rememberMeParameter("rem");复制代码那么:loginProcessingUrl() 这一配置作用是什么?顾名思义 login 请求发送到指定 URL。


如果 loginPage 与表单提交 action 指定的 URL 不一致,则 loginProcessingUrl()的 URL 与 action URL


保持一致,这样就可以将表单进行拦截验证。


小结学习了 Spring Security 的基本使用,以及和 Thymeleaf 进行整合的使用。


实现 WebSecurityConfigurerAdapter 类,自定义 Security 的一些安全策略重写两个方法,configure() ,一个授权,一个认证整合 Thymeleaf 进行前端的设计,根据配置进行权限控制。以及一些整合时的注意事项和细节其主要通过 AOP 进行授权和认证功能的切入。


而后学习的 Shiro 也跟 Security 有相似之处。

用户头像

喜欢就点个关注吧~ 2023-04-10 加入

持续更新Java

评论

发布
暂无评论
Spring Security 的介绍和简单使用_Java_会踢球的程序源_InfoQ写作社区