SpringSecurity是Spring家族中的一个重量级安全管理框架,实际上,在Springboot出现之前,SpringSecurity就已经发展了很多年了。Springboot为springSecurity提供了自动化配置方案。可以零配置使用SpringSecurity。
1.项目的创建
在Springboot中使用SpringSecurity非常容易,创建项目的时候引入Security的依赖即可:
pom文件中的SpringSecurity依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
只要加入依赖,项目所有的接口都会被保护起来
2.体验Springsecurity
创建一个HelloController:
@RestControlle
public class HelloController{
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
启动后访问接口 "/hello" 页面显示
需要登录才能访问。当用户向浏览器发送访问“/hello”接口的请求时,服务端会返回302响应码,让客户重定向到“/login”页面,用户在“/login”页面登录,登录成功后会自动跳转到“/hello”接口。
SpringSecurity支持两种不同的认证方式:
- 可以通过form表单认证
- 可以通过HttpBasic认证
3.用户名密码的配置
默认情况下,登录的用户名“user”,密码是项目启动时随机生成的字符串,可以从启动的控制台中看到默认的密码:
这个随机生成的密码,每次启动的时候都会发生改变。对登录的用户名/密码进行配置,有三种不同的方法
- 在 application.properties 中进行配置
- 通过 Java 代码配置在内存中
- 通过 Java 从数据库中加载
配置文件配置用户名/密码
直接在application.properties文件中配置用户的基本信息
spring.security.user.name=zhulaosan
spring.security.user.password=123
spring.security.user.roles=admin
配置完成后,重启项目,就可以使用这个配置的用户名/密码进行登录
java配置用户名/密码
可以在java代码中配置用户名和密码,首先要创建一个配置类,继承自WebSecurityConfigurerAdapter 类,如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//下面这两行配置表示在内存中配置了两个用户
auth.inMemoryAuthentication()
.withUser("zhulaosan").password("$2a$10$OR3VSksVAmCzc.7WeaRPR.t0wyCsIj24k0Bne8iKWV1o.V9wsP8Xe").roles("admin")
.and()
.withUser("朱老三").password("$2a$10$OR3VSksVAmCzc.7WeaRPR.t0wyCsIj24k0Bne8iKWV1o.V9wsP8Xe").roles("user");
}
在configure方法中配置了两个用户,用户密码都是加密后的字符串(明文都是123),从Spring5开始,强制要求密码加密,如果 不想加密,可以使用使用过期的PasswordEncoder的实例NoOpPasswordEncoder,不建议这么做,不安全。
4.登录配置
对于登录接口,登录成功后的响应,登录失败后的响应,我们都可以在 WebSecurityConfigurerAdapter 的实现类中进行配置。例如下面这样:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhulaosan").password("123").roles("admin")
.and()
.withUser("朱老三").password("123").roles("user");
}
//登录post请求
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//开启登录配置
.antMatchers("/admin/**").hasAnyRole("admin")//表示访问 /admin这个接口,需要具备 admin 这个角色
// .antMatchers("user/**").hasAnyRole("admin","user")//表示访问 /user这个接口,需要具备 admin和user 这个角色
.antMatchers("/user/**").access("hasAnyRole('user','admin')")
.anyRequest().authenticated()//表示剩余的其他接口,登录之后就能访问
.and()
.formLogin()
.loginProcessingUrl("/doLogin")//表单登录接口
//定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
.loginPage("/login")
//定义登录时,用户名的 key,默认为 username
.usernameParameter("uname")
//定义登录时,用户密码的 key,默认为 password
.passwordParameter("passwd")
//登录成功的处理器
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String,Object>map=new HashMap<>();
map.put("status",200);
map.put("msg", authentication.getPrincipal());//获取到登录成功的用户对象
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");
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 CredentialsExpiredException) {
map.put("msg", "密码过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()//和表单登录相关的接口统统都直接通过
.and()
//注销用户的处理器
.logout()
.logoutUrl("/logout")//注销的接口get方法
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req , HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Map<String,Object>map=new HashMap<>();
map.put("status",200);
map.put("msg", "注销登录成功");//获取到登录成功的用户对象
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
.and()
.csrf().disable(); //关闭scr攻击
}
登录失败的Exception在AccountStatusException中可以查看
5.忽略拦截
如果某一个请求地址不需要拦截的话,有两种方式实现:
- 设置该地址匿名访问
- 直接过滤掉该地址,即该地址不走 Spring Security 过滤器链
推荐使用第二种方案,配置如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/vercode");
}
}
6.配置多个 Security
配置多个Security时MultiHttpSecurityConfig可以不继承WebSecurityConfigurerAdapter类
@Configuratio
public class MultiHttpSecurityConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhulaosan").password("$2a$10$G3kVAJHvmRrr6sOj.j4xpO2Dsxl5EG8rHycPHFWyi9UMIhtdSH15u").roles("admin")
.and()
.withUser("朱老三").password("$2a$10$kWjG2GxWhm/2tN2ZBpi7bexXjUneIKFxIAaMYJzY7WcziZLCD4PZS").roles("user");
}
@Configuration
@Order(1)
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/admin/**").authorizeRequests().anyRequest().hasAnyRole("admin");
}
}
@Configuration
public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/doLogin")
.permitAll()
.and()
.csrf().disable();
}
}
}