这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战
一、Spring Security 概述
1.1 框架介绍
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套Web 应用安全性的完整解决方案。
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。 (1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。说人话就是登录认证 (2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。判断是普通用户还是管理员基于功能访问权限
Spring Security其实就是用filter,多请求的路径进行过滤。 (1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。 (2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
1.2 认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
1、用户名密码认证成功,获取用户的权限值;并以用户名为Key,权限列表为Value的形式存储在Redis缓存中
2、根据用户名相关信息返回token,浏览器将token记录到cookie中,没请求都会在请求头中携带token
3、请求中,Spring-security会解析请求头中的token,并获取用户名;
4、根据用户名为key,去redis中获取value值权限列表
5、根据权限列表来判断用户是否有权限访问
二、Demon 讲解
2.1 自定义登录逻辑
配置密码加密类
@Configuration
public class SecurityConfig {
/**
* 设置密码加密
* @return
*/
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
自己定义登录逻辑。
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
private String username = "admin";
private String password = "123";
// 重写 了 userDetailsService 的登录逻辑
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 1、比较用户名
if(!username.equals(s)) {
throw new UsernameNotFoundException("用户名不存在");
}
// 2、比较密码(注册时已经加密过密码) ,这里取出来的是数据库中的加密密码,放入到user 中,security 会自动比较
String newPassword = passwordEncoder.encode(password);
// 3、逗号分割的权限
return new User(username,newPassword, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
2.2 自定义登录页面
虽然 SpringSecurity 给我们提供了登录页面,但我们可以自定义登录页面,修改配置即可.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login.html");
}
这是登录就会跳转到自定义的页面,但这是会存在一个问题,所有的页面都可以访问。需要做限制。
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin().loginPage("/login.html");
// 授权
http.authorizeRequests()
// 所有的请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
}
此时重定向次数过多,因为在跳转login.html 的时候也会被限制,所以需要方向 login.html,不需要认证.
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin().loginPage("/login.html")
// 必须和表单提交的接口一样,执行自定义逻辑
.loginProcessingUrl("/login")
.successForwardUrl("/login");
// 授权
http.authorizeRequests()
// 放行 login.html,不需要认证
.antMatchers("/login.html").permitAll()
// 所有的请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
// 关掉 csrf 防护
http.csrf().disable();
}
2.3 登录失败跳转
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin().loginPage("/login.html")
// 必须和表单提交的接口一样,执行自定义逻辑
.loginProcessingUrl("/login")
.successForwardUrl("/login")
.failureForwardUrl("/toError");
// 授权
http.authorizeRequests()
// 放行 login.html,不需要认证
.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
// 所有的请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
// 关掉 csrf 防护
http.csrf().disable();
}
2.4 设置请求的账户和密码的参数名
登录的账户和密码必须是 password 和 username 。
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表单提交
http.formLogin().loginPage("/login.html")
// 必须和表单提交的接口一样,执行自定义逻辑
.loginProcessingUrl("/login")
.successForwardUrl("/login")
.failureForwardUrl("/toError")
.usernameParameter("user123")
.passwordParameter("pass123");
// 授权
http.authorizeRequests()
// 放行 login.html,不需要认证
.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
// 所有的请求都必须认证才能访问,必须登录
.anyRequest().authenticated();
// 关掉 csrf 防护
http.csrf().disable();
}
2.5 自定义登录成功处理器
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
// 重定向可以跳转到百度
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
自定义登录成功处理器
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
// 重定向可以跳转到百度
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
这时就可以看到登录成功了。
类似的自定义登录失败处理器类似。
2.6 方法讲解
- anyRequest 是放最后面的,所有的任何请求放后面,特殊的请求放前面。
- antMatchers:放行静态资源,不需要认证
- regexMatchers:匹配正则表达式
- permitAll():允许所有
- authenticated:所有都要认证
// 授权
http.authorizeRequests()
// 放行 login.html,不需要认证
.antMatchers("/login.html").permitAll()
.antMatchers("/error.html").permitAll()
.regexMatchers(".+[.]png").permitAll()
// 所有的请求都必须认证才能访问,必须登录
.anyRequest().authenticated();