简介
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求
特征:
- 全面和可扩展的身份验证和授权支持
- 防御会话固定,点击劫持,跨站点请求伪造等攻击
- Servlet API集成
- 与Spring Web MVC的可选集成
- …
Spring Security提供了基于角色的访问控制和访问控制列表(Access Controller List),可以对应用中领域对象进行细粒度的控制。
实战测试
1、实验环境搭建
-
新建一个初始的SpringBoot项目web模块,thymeleaf模块
-
导入静态资源
- welcome.html
- views
- level1
- 1.html
- 2.html
- 3.html
- level2
- 1.html
- 2.html
- 3.html
- level3
- 1.html
- 2.html
- 3.html
- Login.html
- level1
-
controller跳转
@Controller public class RouterController{ @RequestMapping({"/","index"}) public String index(){ return "index"; } @RequestMapping("/toLogin") public String toLogin(){ return "views/login"; } @RequestMapping("/level/{id}") public String level1(@pathVariable("id") int id){ return "views/level1/"+id; } @RequestMapping("/level2/{id}") public String level2(@pathVariable("id") int id){ return "views/level2/"+id; } @RequestMapping("/level3/{id}") public String level3(@pathVariable("id") int id){ return "views/level3/"+id; } }
-
测试实验环境是否正常
2、认识SpringSecurity
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,它可以实现强大的web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfiguterAdapter: 自定义Security策略
- AuthenticationManagerBuilder: 自定义认证策略
- @EnableWebSecurity: 开启WebSecurity模式
”认证“(Authentication)
- 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份
- 身份验证通常通过用户名和密码完成,有时候与身份验证因素结合使用
”授权“(Authorization)
- 授权发生在系统成功验证您的身份之后,最终授予您访问资源(如:信息,文件,数据库,位置等几乎任何内容)的权限
- 这个概念是通用的,而不是只在Spring Security中存在
3、认证和授权
使用Spring Security增加上认证和授权的功能
-
引入Spring Security模块
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
编写Spring Security配置类
参考官网:https://spring.io/projects/spring-security
帮助文档:https://docs.spring.io/spring-security/site/docs/5.4.2/reference/html5/
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .apply(customDsl()) .flag(true) .and() ...; } }
-
编写基础配置类
@Configuration @EnableWebSecurity //开启WebSecurity模式 public class SecurityConfig extends webSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception{ } }
-
定制请求的授权规则
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/") //路径 .permitAll() // 所有用户 .antMatchers("/level1/**").hasRole("vip1") //vip1角色权限 .antMatchers("/level2/**").hasRole("vip2") //vip2角色权限 .antMatchers("/level3/**").hasRole("vip3"); //vip3角色权限 }
-
测试登录
ps:这里登录的用户必须有角色才行
-
在==configure()==方法中加入下面配置,开启自动配置登录功能
//开启自动配置登录功能 //login 请求来到登录页 //login?error 重定向到这里表示登录失败 http.formlogin(); //目前的登录页 是 Spring Security 中 默认的首页
-
运行结果,如果没有权限,会跳转到登录页面
-
定义认证规则,给某些用户添加角色,重写configure(AuthenticationManagerBuilder auth)
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication()//在内存中定义,也可以选择jdbc中获取 .withUser("zhangsan").password("123").roles("vip2","vip3") .and() .withUser("lisi").password("123").roles("vip1") .and() .withUser("wangwu").password("123").roles("vip1","vip2","vip3"); }
-
测试,使用有角色的账号进行登录,发现报错,原因前端传过来的密码必须先加密才能登录
-
给密码加密
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication()//在内存中定义,也可以选择jdbc中获取 .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3") .and() .withUser("lisi").password(new BCryptPasswordEncoder().encode("123")).roles("vip1") .and() .withUser("wangwu").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2","vip3"); }
-
再次登录测试,登录成功,且每个角色只能访问自己认证允许访问的页面。
4、权限控制和注销
-
开启自动配置和注销功能
@Override protected void configure(HttpSecurity http) throws Exception { //注销请求, 也就是登出 http.logout(); }
-
在前端测试页面增加一个注销按钮
<a class="item" th:href="@{/logout}"> <i class="address card icon"></i>注销 </a>
-
重启,测试注销,发现注销完毕后跳转的页面是 Spring Security默认登录页面,如果想要指定登出后的页面怎么办?
-
于是,需要增加一步处理
//增加注销成功跳转页面 logoutSuccessUrl("/") http.logout().logoutSuccessUrl("/");
-
再次测试,发现已经可以跳转到我们指定页面
-
新需求:前端如果根据是否登录以及角色的权限显示不同内容
我们需要结合thymeleaf中的一些功能
sec:authorize=“isAuthenticated()”:是否认证登录! 来显示不同的页面
-
Maven依赖:
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
-
前端页面
-
导入命名空间
xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity5”
-
组件添加 sec:authorize 语句
判断是否登录: sec:authorize="!isAuthenticated()"
登录用户名:sec:authentication=“principal.username”
登录用户角色: sec:authentication=“principal.authorities”
-
-
-
重启测试,页面显示,是我们需要已经进行判断后的页面
-
ps:如果注销出现404,那是因为它默认防止csrf(跨站请求伪造),解决方式有两种:
-
使用post请求
-
Spring Security中关闭csrf功能
http.csrf().disable();
-
5、记住我 Remember Me)
记住我功能是绝大数网站都有提供的功能,目的:方便用户下次进入的时候,不用再次登录
如何实现记住我功能?
-
开启记住我功能
@Override protected void configure(HttpSecurity http) throws Exception { http.rememberMe(); }
-
测试,发现Spring Security 的登录页面出现的 记住我按钮
-
登录,关闭浏览器,再次登录,发现用户依旧存在,实现了记住我功能
-
原理:
- 当选中记住我的时候,登录完,可以查看浏览器的Coookies,发现了remember-me的cookie
- 当点击注销的时候,remember-me 会从浏览器的Cookies中删除
6、首页定制
目前登录页面是Spring Security默认的,如何使用自己定制的首页?
-
在刚才的登录页配置后面指定 loginpage
http.formLogin().loginPage("/toLogin");
-
然后前端也需要指向我们自己定义的 login请求
<a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a>
-
我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式
必须为post:
在 loginPage()源码中的注释上有写明:
<form th:action="@{/login}" method="post"> <div class="field"> <label>Username</label> <div class="ui left icon input"> <input type="text" placeholder="Username" name="username"> <!--记住这里name--> <i class="user icon"></i> </div> </div> <div class="field"> <label>Password</label> <div class="ui left icon input"> <input type="password" name="password"> <!--记住这里name--> <i class="lock icon"></i> </div> </div> <input type="submit" class="ui blue submit button"/> </form>
-
这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看 formLogin() 方法的源
码!我们配置接收登录的用户名和密码的参数!http .formLogin() .usernameParameter("username") //前端登录用户名 name .passwordParameter("password") //前端登录密码 name .loginPage("/toLogin") //进入首页请求 .loginProcessingUrl("/login"); // 首页中: 登陆表单提交请求
-
在登录页增加记住我的多选框
<input type="checkbox" name="remember"> 记住我
-
后端处理
//定制记住我的参数! http.rememberMe().rememberMeParameter("remember");
附件:完整的代码
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {
//定制请求的授权规则
@Override protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/")
.permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//开启自动配置的登录功能:如果没有权限,就会跳转到登录页面!
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/toLogin")
.loginProcessingUrl("/login");
// 登陆表单提交请求
//开启自动配置的注销的功能
// /logout 注销请求
// .logoutSuccessUrl("/"); 注销成功来到首页
http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交 logout请求
http.logout().logoutSuccessUrl("/");
//记住我
http.rememberMe().rememberMeParameter("remember");
}
//定义认证规则
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿....
//Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
//要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过 来的密码进行某种方式加密
//spring security 官方推荐的是使用bcrypt加密方式。
auth
.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest")
.password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
}
番外
Spring Security 连接数据库 查询认证 关键代码
- 配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SecurityProvider securityProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(securityProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/") //路径
.permitAll() // 所有用户
.antMatchers("/level1/**").hasRole("vip1") //vip1角色权限
.antMatchers("/level2/**").hasRole("vip2") //vip2角色权限
.antMatchers("/level3/**").hasRole("vip3"); //vip3角色权限
http.rememberMe();
}
}
- 供应类
@Component
public class SecurityProvider implements AuthenticationProvider {
@Autowired
private MyUserDetailService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
UserDetails userDetails = userDetailsService.loadUserByUsername(token.getName()); //从数据库获取用户详情
if (userDetails == null) {
throw new UsernameNotFoundException("找不到该用户");
}
if(!userDetails.getPassword().equals(token.getCredentials().toString()))
{
throw new BadCredentialsException("密码错误");
}
return new UsernamePasswordAuthenticationToken(userDetails,
userDetails.getPassword(), //用户密码
userDetails.getAuthorities() //用户权限
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}