1.什么是Spring Security
Spring Security: Spring Security
是目前Java Web领域中最流行的框架之一,它提供了一系列安全级别,包括基于认证和授权的安全保护,以及各种各样的安全校验,使得开发人员可以非常容易地为应用程序添加安全保护。Spring Security
也提供了一种简单易用的方式来定制其过滤器链,以适应具体的安全需求。
(1)在用户认证方面,Spring Security
框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID
和 LDAP
等。
(2)在用户授权方面,Spring Security
提供了基于角色的访问控制和访问控制列表(Access Control List,ACL
),可以对应用中的领域对象进行细粒度的控制。
2.工作原理和特点
原理:
认证:认证模块负责验证用户身份的合法性,生成认证令牌,并保存到服务端会话中(如TLS)。
鉴权:鉴权模块负责从服务端会话内获取用户身份信息,与访问的资源进行权限比对。
特点:
-
兼容性强:
Spring Security
是一个流行的开源框架,它可以与Spring应用程序完美集成。由于它的兼容性很好,因此可以非常方便地使用它保护Web应用程序。 -
功能强大:
Spring Security
具备众多功能,包括注销、登录、角色、权限、令牌、XSS
防御、CSRF
防御等等。它还支持各种身份验证、角色和权限管理方式,如基于表单的认证、基于记住我功能的认证以及OAuth
认证等等。 -
安全可靠:
Spring Security
具有极高的安全性,它使用最新的安全标准和协议来保护Web应用程序。Spring Security
采用安全性分层的策略来保护应用程序中的各个层,例如Web
层、Service
层、DAO
层等等。除此之外,Spring Security
还支持自定义安全策略和事件响应,从而使得开发者可以根据应用程序需求定制安全保护。 -
易于使用:
Spring Security
提供了一种高度简化的方式来保护Web应用程序。它使用简单的标签和安全注解来添加安全保护,从而使得开发者可以基本不需要手动编写代码就可以完成安全保护。 -
社区广泛:
Spring Security
是一个著名的开源框架,因此它有一个庞大的用户社区。这个社区不仅提供了大量的文档、示例和教程,而且还会解答开发者的问题、修复框架中的BUG等等。这为开发者提供了无限的支持和帮助,从而可以使用Spring Security
更加自信。
3.代码实战入门
1.基于springboot创建项目
(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
(2)配置yml文件
spring:
freemarker:
suffix: .ftl
template-loader-path: classpath:/templates/
enabled: true
(3)创建控制类,定义请求处理方法
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello,spring security";
}
}
(4)启动项目,系统会自动生成一个默认的随机登录密码(因为当前没有配置用户信息,配置之后就不会在生成默认登录密码)
注:这是 Spring Security
为默认用户 user 生成的临时密码,是一个 UUID 字符串。
(5)访问登录页并进行测试
(6)在yml
文件中配置自定义用户名和密码。
spring:
security:
user:
name: admin
password: 123456
配置成功后,重启服务并进行测试
4.实现自定义的登录
1.自定义一个登录页面
<h1>用户登录</h1>
<form action="/userLogin" method="post">
<label>用户:</label><input type="text" name="username"/><br/>
<label>密码:</label><input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
主页:
<body>
<h1>Welcome</h1>
<h2>Spring Security</h2>
<div style="position: absolute;top:15px;right:15px;">
<a href="/toLogin">用户登录</a>
<a href="/logout" onclick="return confirm('确认退出吗?');">安全退出</a>
</div>
<h4>管理员</h4>
<ul>
<li><a href="/admin/toAddUser">新增用户</a></li>
<li><a href="/admin/toListUser">用户列表</a></li>
<li><a href="/admin/toResetPwd">重置密码</a></li>
<li><a href="/admin/toUpdateUser">修改用户</a></li>
</ul>
<ul>
<h4>普通用户</h4>
<li><a href="/user/toUpdatePwd">修改密码</a></li>
</ul>
</body>
2.创建控制层
@Controller
public class UserController {
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/userLogin")
public String userLogin(String username,String password){
System.out.println("username="+username+",password="+password);
return "index";
}
@RequestMapping("/admin/toAddUser")
public String toAddUser(){
return "admin/addUser";
}
@RequestMapping("/admin/toListUser")
public String toListUser(){
return "admin/listUser";
}
@RequestMapping("/admin/toResetPwd")
public String toResetPwd(){
return "admin/resetPwd";
}
@RequestMapping("/admin/toUpdateUser")
public String toUpdateUser(){
return "admin/updateUser";
}
@RequestMapping("/user/toUpdatePwd")
public String toUpdatePwd(){
return "user/updatePwd";
}
}
3.创建配置类
进行一个自定义登录的配置之后加入一个Spring Security的安全退出设置,然后可以配置多用户角色的访问,通过修改SecurityConfig配置类,可以实现多用户的角色配置,再配置一个自定义异常处理器,设置不同角色的访问权限
注:在配置多用户角色访问的时候需要删除或注释yml文件中配置的自定义用户名和密码
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public PasswordEncoder bcryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//多用户角色配置 注意:此处采用的是基于内存方式储存身份和授权信息。
@Bean
public UserDetailsService userDetailsService(){
UserDetails admin = User.withUsername("admin")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("ADMIN", "USER").build();
UserDetails user = User.withUsername("user")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("USER").build();
return new InMemoryUserDetailsManager(admin,user);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 开放接口访问权限,不需要登录授权就可以访问
.antMatchers("/hello","/","/toLogin").permitAll()
// 设置角色权限
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN","USER")
// 其余所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.formLogin()
// 设置登录页面的 URL
.loginPage("/toLogin")
// 设置登录请求的 URL,即表单提交的 URL
.loginProcessingUrl("/userLogin")
// 设置登录表单中用户名字段的参数名,默认为username
.usernameParameter("username")
// 设置登录表单中密码字段的参数名,默认为password
.passwordParameter("password")
.and()
.logout()
// 设置安全退出的URL路径
.logoutUrl("/logout")
// 设置退出成功后跳转的路径
.logoutSuccessUrl("/");
//添加关闭`csrf`配置
http.csrf().disable();
return http.build();
}
没有访问权限的页面
<body>
<h1>未授权的操作</h1>
<div>
未授权的操作,请与管理员联系,或切换账号重新
<a href="/logout" onclick="return confirm('确认退出吗?');">登录</a>
后再试!
</div>
</body>
异常说明:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
解决方案:这个错误通常是因为在使用 Spring Security
进行密码验证时没有正确地配置密码编码器。在 Spring Security
中,密码编码器用于将用户提供的密码编码为安全的散列值,并将其与存储在数据库中的散列值进行比较。如果没有正确地配置密码编码器,则 Spring Security
将无法识别密码编码器的类型,并在比较密码时引发“没有映射到 id“null”的密码编码器”异常。要解决这个问题,需要在 Spring Security
配置中配置一个密码编码器,并将其用于验证用户提供的密码。常用的密码编码器有 BCryptPasswordEncoder
和 StandardPasswordEncoder
。
最后通过切换不同的用户(user
和admin
)来测试角色权限是否授权成功,admin
账号访问所有接口权限正常;切换到user
账号访问时,发现以/admin/**
开头的接口不能访问(证明鉴权成功),但是却提示403错误。
解决方案:配置自定义异常处理器
修改SecurityConfig
配置类,添加自定义异常处理,并设置异常处理页面
http.exceptionHandling().accessDeniedPage("/noauth")
重启项目后发现无权限将跳转到自定义异常页面。