0.什么是Spring Security
一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。
1、项目创建
导入Spring Web依赖和Spring Security依赖
2、编写一个HelloController的类
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello security!";
}
}
3、启动项目,在浏览器地址栏输入http://localhost:8080/hello,会出现如下页面,这是加了Spring Security依赖后要进行登录验证,usernamem默认是user,密码再springboot启动日志下面有一段字符串,也可以自己配置登录用户名和密码,可以通过配置文件配置,也可以通过配置类进行配置
4、配置文件配置用户名/密码
#配置登录密码
spring.security.user.password=123
#配置登录用户名
spring.security.user.name=lqg
#配置角色名称
spring.security.user.roles=admin
5、Java 配置用户名/密码
也可以在 Java 代码中配置用户名密码,首先需要我们创建一个 Spring Security 的配置类,集成自 WebSecurityConfigurerAdapter 类,如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//告诉系统我的密码不加密,输入账号和密码就可以登录上来
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("javalqg").password("123").roles("admin")
.and()
.withUser("凌枫").password("456").roles("user");
}
}
这里我们在 configure 方法中配置了两个用户,用户的密码都是加密之后的字符串(明文是 123),从 Spring5 开始,强制要求密码要加密,如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,但是不建议这么做,毕竟不安全。
Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存盐
的字段了,这一点比 Shiro 要方便很多。
6.登录配置
对于登录接口,登录成功后的响应,登录失败后的响应,我们都可以在 WebSecurityConfigurerAdapter 的实现类中进行配置。例如下面这样:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//告诉系统我的密码不加密,输入账号和密码就可以登录上来
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("javalqg").password("123").roles("admin")
.and()
.withUser("凌枫").password("456").roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//开启配置
http.authorizeRequests()
//如果路径规则符合/admin/**,就会具备admin角色
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").access("hasAnyRole('user','admin')")
//如果路径规则符合/user/**,就会具备多个角色
.antMatchers("/user/**").hasAnyRole("admin","user")
//剩下的其他请求都是登录之后就能访问的
.anyRequest().authenticated()
.and()
//表单登录
.formLogin()
//处理表单登录的url路径
.loginProcessingUrl("/doLogin")
//默认看到的登录页面,如果是前后端分离的话,就不用配置登录页面
.loginPage("/login")
//修改默认登录的username
.usernameParameter("uname")
//修改默认登录的password
.passwordParameter("passwd")
//登录成功的处理器 authentication:保存登录成功的用户信息
.successHandler(new AuthenticationSuccessHandler() {
/**登录成功的处理*/
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
//设置响应的数据类型
resp.setContentType("application/json;charset=utf-8");
//拿到witer
PrintWriter out = resp.getWriter();
Map<String,Object> map = new HashMap<>();
map.put("status",200);
//获取登录成功的用户对象
map.put("msg",authentication.getPrincipal());
//把map弄成json字符串写出去
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
/**登录失败的处理*/
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
//设置响应的数据类型
resp.setContentType("application/json;charset=utf-8");
//拿到witer
PrintWriter out = resp.getWriter();
Map<String,Object> map = new HashMap<>();
map.put("status",401);
if (e instanceof LockedException){
map.put("msg","账户被锁定,登录失败");
}else if (e instanceof BadCredentialsException){
map.put("msg","用户名或密码输入错误,登录失败!");
}else if (e instanceof DisabledException){
map.put("msg","账户被禁用,登录失败");
}else if (e instanceof AccountExpiredException){
map.put("msg","账户过期,登录失败");
}else if (e instanceof CredentialsExpiredException){
map.put("msg","密码过期,登录失败");
}else {
map.put("msg","登录失败!");
}
//把map弄成json字符串写出去
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
//跟登录相关的接口就能直接访问
.permitAll()
.and()
//关闭csrf攻击
.csrf().disable();
}
}
我们可以在 successHandler 方法中,配置登录成功的回调,如果是前后端分离开发的话,登录成功后返回 JSON 即可,同理,failureHandler 方法中配置登录失败的回调,logoutSuccessHandler 中则配置注销成功的回调。