关于node.js:Spring-Security入门学习

5次阅读

共计 11779 个字符,预计需要花费 30 分钟才能阅读完成。

意识 Spring Security
Spring Security 是为基于 Spring 的应用程序提供申明式平安爱护的安全性框架。Spring Security 提供了残缺的安全性解决方案,它可能在 Web 申请级别和办法调用级别解决身份认证和受权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入(dependency injection,DI)和面向切面的技术。
外围性能
对于一个权限治理框架而言,无论是 Shiro 还是 Spring Security,最最外围的性能,无非就是两方面:

认证
受权

艰深点说,认证就是咱们常说的登录,受权就是权限甄别,看看申请是否具备相应的权限。
认证(Authentication)
Spring Security 反对多种不同的认证形式,这些认证形式有的是 Spring Security 本人提供的认证性能,有的是第三方规范组织制订的,次要有如下一些:
一些比拟常见的认证形式:

HTTP BASIC authentication headers:基于 IETF RFC 规范。
HTTP Digest authentication headers:基于 IETF RFC 规范。
HTTP X.509 client certificate exchange:基于 IETF RFC 规范。
LDAP:跨平台身份验证。
Form-based authentication:基于表单的身份验证。
Run-as authentication:用户用户长期以某一个身份登录。
OpenID authentication:去中心化认证。

除了这些常见的认证形式之外,一些比拟冷门的认证形式,Spring Security 也提供了反对。

Jasig Central Authentication Service:单点登录。
Automatic “remember-me” authentication:记住我登录(容许一些非敏感操作)。
Anonymous authentication:匿名登录。
……

作为一个凋谢的平台,Spring Security 提供的认证机制不仅仅是下面这些。如果下面这些认证机制仍然无奈满足你的需要,咱们也能够本人定制认证逻辑。当咱们须要和一些“老破旧”的零碎进行集成时,自定义认证逻辑就显得十分重要了。
受权(Authorization)
无论采纳了下面哪种认证形式,都不影响在 Spring Security 中应用受权性能。Spring Security 反对基于 URL 的申请受权、反对办法拜访受权、反对 SpEL 访问控制、反对域对象平安(ACL),同时也反对动静权限配置、反对 RBAC 权限模型等,总之,咱们常见的权限治理需要,Spring Security 基本上都是反对的。
我的项目实际
创立 maven 工程
我的项目依赖如下:
<dependencies>
<!– 以下是 >spring boot 依赖 –>
<dependency>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!– 以下是 >spring security 依赖 –>
<dependency>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>

</dependency>
</dependencies>
复制代码
提供一个简略的测试接口,如下:
@RestController
public class HelloController {

@GetMapping(“/hello”)
public String hello() {

return "hello,hresh";

}

@GetMapping(“/hresh”)
public String sayHello() {

return "hello,world";

}
}
复制代码
再创立一个启动类,如下:
@SpringBootApplication
public class SecurityInMemoryApplication {

public static void main(String[] args) {

SpringApplication.run(SecurityInMemoryApplication.class, args);

}
}
复制代码
在 Spring Security 中,默认状况下,只有增加了依赖,咱们我的项目的所有接口就曾经被通通爱护起来了,当初启动我的项目,拜访 /hello 接口,就须要登录之后才能够拜访,登录的用户名是 user,明码则是随机生成的,在我的项目的启动日志中,如下所示:
Using generated security password: 21596f81-e185-4b6a-a8ff-1b21e2a60c6f
复制代码
咱们尝试拜访 /hello 接口,因为该接口被 Spring Security 爱护起来了,重定向到 /login 接口,如下图所示:

输出账号和明码后,即可拜访 /hello 接口。
那么如何自定义登录用户信息呢?以及 Spring Security 如何晓得咱们想要反对基于表单的身份验证?
认证
启用 web 安全性性能
Spring Security 提供了用户名明码登录、退出、会话治理等认证性能,只须要配置即可应用。
在 Spring Security 5.7 版本之前,或者 SpringBoot2.7 之前,咱们都是继承 WebSecurityConfigurerAdapter 来配置。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

// 定义用户信息服务(查问用户信息)
@Bean
public UserDetailsService userDetailsService() {

InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;

}

// 明码编码器, 不加密,字符串间接比拟
@Bean
public PasswordEncoder passwordEncoder() {

return NoOpPasswordEncoder.getInstance();

}

@Override
public void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers("/hello");

}

// 平安拦挡机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {

http
  .authorizeRequests()
  .anyRequest().authenticated()
  .and()
  .formLogin()
  .and()
  .httpBasic();

}
}
复制代码
Spring Security 提供了这种链式的办法调用。下面配置指定了认证形式为 HTTP Basic 登录,并且所有申请都须要进行认证。
这里有一点须要留神,我没并没有在 Spring Security 配置类上应用 @EnableWebSecurity 注解。这是因为在非 Spring Boot 的 Spring Web MVC 利用中,注解 @EnableWebSecurity 须要开发人员本人引入以启用 Web 平安。而在基于 Spring Boot 的 Spring Web MVC 利用中, 开发人员没有必要再次援用该注解,Spring Boot 的主动配置机制 WebSecurityEnablerConfiguration 曾经引入了该注解,如下所示:
package org.springframework.boot.autoconfigure.security.servlet;

// 省略 imports 行

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {“springSecurityFilterChain”}
)
@ConditionalOnClass({EnableWebSecurity.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableWebSecurity
class WebSecurityEnablerConfiguration {
WebSecurityEnablerConfiguration() {
}
}
复制代码
实际上,一个 Spring Web 利用中,WebSecurityConfigurerAdapter 可能有多个 , @EnableWebSecurity 能够不必在任何一个 WebSecurityConfigurerAdapter 上,能够用在每个 WebSecurityConfigurerAdapter 上,也能够只用在某一个 WebSecurityConfigurerAdapter 上。多处应用 @EnableWebSecurity 注解并不会导致问题,其最终运行时成果跟应用 @EnableWebSecurity 一次成果是一样的。
在 userDetailsService()办法中,咱们返回了一个 UserDetailsService 给 Spring 容器,Spring Security 会应用它来获取用户信息。咱们临时应用 InMemoryUserDetailsManager 实现类,并在其中别离创立了 zhangsan、lisi 两个用户,并设置明码和权限。
在 configure(HttpSecurity http)办法中进入如下配置:

确保对咱们的应用程序的任何申请都要求用户进行身份验证
容许用户应用基于表单的登录进行身份验证
容许用户应用 HTTP 根本身份验证进行身份验证

留神上述还有一个 passwordEncoder()办法,在 IDEA 中会提醒 NoOpPasswordEncoder 已过期。这是因为 Spring Security 5 对 PasswordEncoder 做了相干的重构,原先默认配置的 PlainTextPasswordEncoder(明文明码)被移除了,想要做到明文存储明码,只能应用一个过期的类来过渡。
// 退出
// 已过期
@Bean
PasswordEncoder passwordEncoder(){

return NoOpPasswordEncoder.getInstance();

}
复制代码
Spring Security 提供了多品种来进行明码编码,并作为了相干配置的默认配置,只不过没有裸露为全局的 Bean。在理论利用中应用明文校验明码必定是存在危险的,NoOpPasswordEncoder 只能存在于 demo 中。
// 理论利用
@Bean
PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

// 加密形式与对应的类
bcrypt – BCryptPasswordEncoder (Also used for encoding)
ldap – LdapShaPasswordEncoder
MD4 – Md4PasswordEncoder
MD5 – new MessageDigestPasswordEncoder(“MD5”)
noop – NoOpPasswordEncoder
pbkdf2 – Pbkdf2PasswordEncoder
scrypt – SCryptPasswordEncoder
SHA-1 – new MessageDigestPasswordEncoder(“SHA-1”)
SHA-256 – new MessageDigestPasswordEncoder(“SHA-256”)
sha256 – StandardPasswordEncoder
复制代码
然而在 Spring Security 5.7 版本之后(包含 5.7 版本),或者 SpringBoot2.7 之后,WebSecurityConfigurerAdapter 就过期了,尽管能够持续应用,但看着比拟顺当。
看 5.7 版本官网文档是如何解释的:

以前咱们自定义类继承自 WebSecurityConfigurerAdapter 来配置咱们的 Spring Security,咱们次要是配置两个货色:

configure(HttpSecurity)
configure(WebSecurity)

前者次要是配置 Spring Security 中的过滤器链,后者则次要是配置一些门路放行规定。
当初在 WebSecurityConfigurerAdapter 的正文中,人家曾经把意思说的很明确了:

当前如果想要配置过滤器链,能够通过自定义 SecurityFilterChain Bean 来实现。
当前如果想要配置 WebSecurity,能够通过 WebSecurityCustomizer Bean 来实现。

咱们对上文中的 SecurityConfig 文件做一下改变,试试新版中该如何配置。
@Configuration
public class SecurityConfig {

@Bean
public UserDetailsService userDetailsService() {

InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;

}

@Bean
public PasswordEncoder passwordEncoder() {

return NoOpPasswordEncoder.getInstance();

}

@Bean
WebSecurityCustomizer webSecurityCustomizer() {

return web -> web.ignoring().antMatchers("/hello");

}

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

http.authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin()
    .permitAll()
    .and()
    .csrf().disable();
return http.build();

}
}
复制代码
此时重启我的项目,你会发现 /hello 也是能够间接拜访的,就是因为这个门路不通过任何过滤器。
集体感觉新写法更加直观,能够分明的看到 SecurityFilterChain 是对于过滤器链配置的,与咱们理论知识提到的过滤器常识是统一的。
测试
拜访 http://localhost:8086/hello,能够间接看到页面内容,不须要输出账号密码。
拜访 http://localhost:8086/hresh,则须要账号密码,即咱们配置的 zhangsan 和 lisi 用户。
在测试过程中,你可能会发现这样几个问题:
1、间接拜访 http://localhost:8086,默认会跳转到 /login 页面,该配置位于 UsernamePasswordAuthenticationFilter 类文件中,如果你想自定义登录页面,能够这样批改:

http.authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin()

// .loginPage(“/login.html”)

    .loginProcessingUrl("/login")

复制代码
2、表单登录时,账号密码默认字段为 username 和 password。
3、按理来说,登录胜利之后是跳到 / 页面,失败跳转到登录页,但因为咱们这是 SpringBoot 我的项目,咱们能够让它登录胜利时返回 json 数据,而不是重定向到某个页面。默认状况下,账号密码输出谬误会主动返回登录页面,所以此处咱们就不解决失败的状况。
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

private static ObjectMapper objectMapper = new ObjectMapper();

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,

  Authentication authentication) throws IOException {response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString("登录胜利"));

}
}
复制代码
接着批改 securityFilterChain()办法
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(myAuthenticationSuccessHandler)
.and()
.csrf().disable();
复制代码
再次重启我的项目,在登录页面输出账号密码后,返回后果如下所示:

4、自定义登录页面,在 resource 目录下新建 static 目录,外面增加 login.html 文件,临时未增加款式
<!DOCTYPE html>
<html>
<head>
<meta charset=”UTF-8″>
<title> 登录 </title>
</head>
<body>
<form action=”/doLogin” method=”post”>
<div class=”input”>

<label for="name"> 用户名 </label>
<input type="text" name="name" id="name">
<span class="spin"></span>

</div>
<div class=”input”>

<label for="pass"> 明码 </label>
<input type="password" name="passwd" id="pass">
<span class="spin"></span>

</div>
<div class=”button login”>

<button type="submit">
  <span> 登录 </span>
  <i class="fa fa-check"></i>
</button>

</div>
</form>
</body>
</html>
复制代码
批改 securityFilterChain()办法
http.authorizeRequests() // 示意开启权限配置
.antMatchers(“/login.html”).permitAll()
.anyRequest().authenticated() // 示意所有的申请都要通过认证之后能力拜访
.and() // 链式编程写法
.formLogin() // 开启表单登录配置
.loginPage(“/login.html”) // 配置登录页面地址
.loginProcessingUrl(“/doLogin”)
.permitAll()
.and()
.csrf().disable();
复制代码
重启我的项目后,再次拜访 http://localhost:8086/,会重定向到 http://localhost:8086/login.html。
最初,总结一下 HttpSecurity 的配置,示例如下:

http.authorizeRequests()  // 示意开启权限配置
    .anyRequest().authenticated() // 示意所有的申请都要通过认证之后能力拜访
    .and()  // 链式编程写法
    .formLogin()  // 开启表单登录配置
    .loginPage("/login.html") // 配置自定义登录页面地址
    .loginProcessingUrl("/login") // 配置登录接口地址

// .defaultSuccessUrl() // 登录胜利后的跳转页面
// .failureUrl() // 登录失败后的跳转页面
// .usernameParameter(“username”) // 登录用户名的参数名称
// .passwordParameter(“password”) // 登录明码的参数名称
// .successHandler(
// myAuthenticationSuccessHandler) // 前后端拆散的状况,并不想通过 defaultSuccessUrl 进行页面跳转,只须要返回一个 json 数据来告知前端
// .failureHandler(myAuthenticationFailureHandler) // 同理,代替 failureUrl
// .permitAll()

    .and()
            .csrf().disable();// 禁用 CSRF 进攻性能,测试能够先敞开

复制代码
表单验证时,loginPage 与 loginProcessingUrl 区别:

loginPage 配置自定义登录页面地址,loginProcessingUrl 默认与表单 action 地址统一;
如果只配置 loginPage 而不配置 loginProcessingUrl,那么 loginProcessingUrl 默认就是 loginPage;
如果只配置 loginProcessUrl,就会用不了自定义登陆页面,Security 会应用自带的默认登陆页面;

如果 loginProcessingUrl 默认与表单 action 地址不统一,那么它须要指向一个无效的地址,比如说 /doLogin.html,这要求咱们在 static 目录下创立一个 doLogin.html 页面,此外,还须要在 controller 文件中减少如下办法:
@PostMapping(“/doLogin”)
public String doLogin() {

return "我登录胜利了";

}
复制代码
然而登录胜利后并不会显示 doLogin.html 页面的内容,而是显示 /doLogin 的返回后果。同理,不配置 loginProcessingUrl,那么 loginProcessingUrl 默认就是 loginPage,即 loginProcessingUrl=login.html,与 doLogin.html 成果一样。
另外再介绍一下 Spring Security 中 defaultSuccessUrl 和 successForwardUrl 的区别:
假设在 defaultSuccessUrl 中指定登录胜利的跳转页面为 /index,那么存在两种状况:

① 浏览器中输出的是登录地址,登录胜利后,则间接跳转到 /index;
② 如果浏览器中输出了其余地址,例如 http://localhost:8080/elseUrl,若登录胜利,就不会跳转到 /index,而是来到 /elseUrl 页面。

defaultSuccessUrl 就是说,它会默认跳转到 Referer 起源页面,如果 Referer 为空,没有起源页,则跳转到默认设置的页面。
successForwardUrl 示意不论 Referer 从何而来,登录胜利后一律跳转到指定的地址。
认证形式抉择
在 WebSecurityConfigurerAdapter 类中有很多 configure()办法,除了上文提到的 HttpSecurity 和 WebSecurity 参数,还有一个 AuthenticationManagerBuilder 参数,源码如下:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}

protected AuthenticationManager authenticationManager() throws Exception {
if (!this.authenticationManagerInitialized) {

this.configure(this.localConfigureAuthenticationBldr);
if (this.disableLocalConfigureAuthenticationBldr) {this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
} else {this.authenticationManager = (AuthenticationManager)this.localConfigureAuthenticationBldr.build();}

this.authenticationManagerInitialized = true;

}

return this.authenticationManager;
}
复制代码
该类用于设置各种用户想用的认证形式,设置用户认证数据库查问服务 UserDetailsService 类以及增加自定义 AuthenticationProvider 类实例等
Spring Security 为配置用户存储提供了多个可选解决方案,包含:

基于内存的用户存储
基于 JDBC 的用户存储
以 LDAP 作为后端的用户存储
自定义用户详情服务

对于这四种形式就不具体介绍了,能够重点关注计划二和计划四,而在本我的项目中,咱们间接在 SecurityConfig 中重写 userDetailsService 办法,并将 UserDetailsService 对象注入到 Spring 容器中。
受权
1、首先在 HelloController 中减少 r1 和 r2 资源。
@GetMapping(value = “/r/r1”)
public String r1() {
return ” 拜访资源 1 ″;
}

@GetMapping(value = “/r/r2”)
public String r2() {
return ” 拜访资源 2 ″;
}
复制代码
2、批改 SecurityConfig 文件中的 securityFilterChain() 办法
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()

.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(myAuthenticationSuccessHandler)
.permitAll()
.and()
.csrf().disable();

return http.build();
}
复制代码
拜访 r1、r2 资源,须要对应的权限,而且其余接口则只须要认证,并不需要受权。
3、测试
拜访 http://localhost:8086,进入登录页面,输出正确的账号密码,提交后页面返回“登录胜利”,如果是 zhangsan,则能够拜访 r1 资源,拜访 r2 则会报错,咱们临时未处理错误如下:

总结
对于 Spring Security 的学习先到这里,根本理解如何应用即可,咱们持续前面的学习。
如果想要深刻学习 Spring Security,举荐浏览《深入浅出 Spring Security》,还包含配套的代码示例。

正文完
 0