介绍
Spring Security 是一个高度自定义的安全框架。利用 Spring IoC/DI 和 AOP 功能,为系统提供了声明式安全访问控制功能,减少了为系 统安全而编写大量重复代码的工作。主要实现两个功能:
- 用户登录的控制
- 登录后权限的控制
使用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入了依赖,其实就已经完成了配置,当我请求我们所要进入的页面时,都会先进行登录的验证,才会跳转到我们访问的页面。但是在实际的使用中,我们一般会跳转到我们请求的页面做安全的控制以及权限的控制,因此我们需要自定义对应的类。
UserDetailsService接口
当什么也没有配置的时候,账号和密码是由 Spring Security 定义 生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所 以我们要通过自定义逻辑控制认证逻辑。
三个参数具体解释:
username:用户名,这里默认提交表单中的 name 必须叫username
password:密码
authorities:用户具有的权限。此处不允许为 null
此处的用户名应该是客户端传递过来的用户名。而密码应该是从 数据库中查询出来的密码。Spring Security 会根据 User 中的 password 和客户端传递过来的 password 进行比较。如果相同则表示认证通过, 如果不相同表示认证失败。authorities 里面包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做 某个事情时必须包含某个权限则会出现 403。通常都是通过 AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来 创 建 authorities 集合对象的。参数时一个字符串,多个权限使用逗号分隔。
如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可,就可以进行对应的登录配置。
BCryptPasswordEncoder
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是 基于 Hash 算法实现的单向加密。
encode():把参数按照特定的解析规则进行解析。
matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。 第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
//加密的使用
public void test(){
BCryptPasswordEncoder pe = new BCryptPasswordEncoder();
String encode = pe.encode("123");//加密
System.out.println(encode);
boolean matches = pe.matches("1234", encode);//原数据和加密匹配
System.out.println(matches);//false
}
自定义登录逻辑
当进行自定义登录逻辑时需要用到之前讲解的 UserDetailsService 和 PasswordEncoder。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。因此通过创建配置类注入该对象
- PasswordEncoder配置类
@Configuration
public class SecurityConfig{
@Bean
public PasswordEncoder getPe() {
return new BCryptPasswordEncoder();
}
}
- 编写登录用户逻辑,需要实现UserDetailsService
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库,查询用户名是否存在,如果不存在抛出UsernameNotFoundException
if (!username.equals("admin")){
throw new UsernameNotFoundException("用户名不存在");
}
//2.把查询出来的密码进行解析,或直接把构造方法放到构造方法中
//password 就是数据库中查询出来的密码,查询内容不是 123
String password = encoder.encode("123");//加密后的数据,这里是模拟数据库中的密码
//三个参数:用户名,数据库加密后的密码,实现的权限,角色,可访问的页面
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_adminN,/main.html,/main1.html"));
}
}
自定义页面逻辑
简单说就是,你要设置那个页面做主页,那些页面不用验证,那些页面需要认证,那个角色或者权限能访问那个页面
该。配置类需要继承 WebSecurityConfigurerAdapte,并重写 configure 方法。
-
常用的配置中调用的方法
successForwardUrl()登录成功后跳转地址,使用 successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler()方法进行控制成功后交给哪个类进行处理。ForwardAuthenticationSuccessHandler 内部就是最简单的请求转发。由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。
当需要控制登录成功后去做一些事情时,可以进行自定义认证成功控制器。这里以成功控制器为例,自定义认证失败控制器只需要继承AuthenticationFailureHandler即可
//自定义登录成功处理器 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 { System.out.println(httpServletRequest.getRemoteAddr()); User user = (User) authentication.getPrincipal(); System.out.println(user.getPassword());//默认 null System.out.println(user.getAuthorities());//权限 httpServletResponse.sendRedirect(url); } }
loginPage() 登录页面
loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。
antMatchers():匹配内容 permitAll():允许
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; @Autowired private UserDetailServiceImpl userDetailService; @Autowired private DataSource dataSource; @Autowired private PersistentTokenRepository repository; @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("执行UserDetailServiceImpl"); //表单认证(登录) http.formLogin() .loginProcessingUrl("/login")//当发现/login时认为是登录,需要执行UserDetailServiceImpl .successForwardUrl("/toMain")//此处是一个 post 请求不能写.html 页面,静态资源只能走 get 请求 //自定义跳转 // .successHandler(new MyAuthenticationSuccessHandler("/main.html")) .failureHandler(new MyAuthenticationFailHandler("/fail.html")) //.failureForwardUrl("/fail") .loginPage("/showLogin");//登录页面 //url 拦截(授权) http.authorizeRequests() .antMatchers("/showLogin", "/fail.html").permitAll()//login.html 不需要被认证 // .mvcMatchers("/login.html").servletPath("/hello").permitAll() // .antMatchers("/main1.html").hasIpAddress("0:0:0:0:0:0:0:1")//具有其中一个就能访问 //.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");//所有的请求都必须被认证,也就是必须登录后才能访问 .anyRequest().authenticated(); //关闭 csrf // http.csrf().disable(); //异常 http.exceptionHandling() .accessDeniedHandler(myAccessDeniedHandler); http.rememberMe() //.tokenValiditySeconds()设置有效时间默认 2 周 .userDetailsService(userDetailService)//用户登录逻辑写在那个对象中 .tokenRepository(repository); //退出 http.logout() .logoutSuccessUrl("/showLogin"); } @Bean public PersistentTokenRepository getPer(){ JdbcTokenRepositoryImpl jt = new JdbcTokenRepositoryImpl(); jt.setDataSource(dataSource);//需要一个数据源 //jt.setCreateTableOnStartup(true);//第一次需要使用 return jt; }