Spring Security 是一款优秀的安全框架,使用少量的配置就可以对我们的Web应用进行防护;随着前端框架的成熟,前后端分离项目越来越多,所以我们介绍使用SpringSecurity保护RestAPI。
一.简易配置
1.在mavan中引入spring-boot-starter-security
-
引入spring-boot-starter-security依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2.SpringSecurity配置概览
- 使用
@EnableWebSecurity
激活SpringSecurity的web保护 - 继承
WebSecurityConfigurerAdapter
覆盖原有web适配器 - 覆写
protected void configure(HttpSecurity http)
覆盖默认访问配置 - 覆写
public void configure(AuthenticationManagerBuilder web)
自定义我们自己的用户数据源 - 提供
BCryptPasswordEncoder
密码加密处理器 - 初步配置如下:
@EnableWebSecurity public class SimpleSecurityJavaConfig extends WebSecurityConfigurerAdapter { @Override public void configure(AuthenticationManagerBuilder web) throws Exception { //使用内存管理器提供用户信息 web.inMemoryAuthentication() .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN") .and() .withUser("user").password(encoder().encode("userPass")).roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()//关闭csrf认证 .authorizeRequests()//配置请求接口权限开始 .antMatchers("/api/foos/**").authenticated()//foos下接口进行了权限认证后就可以访问了 .antMatchers("/api/admin/**").hasRole("ADMIN")//此接口需要拥有ADMIN权限的用户才能访问 .and()//连接符 .formLogin();//使用表单类型提交鉴权 } @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } }
3.验证上述配置
-
创建controller接口
@Controller @RequestMapping(value = "/api/admin") public class AdminController { @RequestMapping(value = "/hi", method = RequestMethod.GET) @ResponseBody public String hi() { return "hey guys!"; } } @Controller @RequestMapping(value = "/api/foos") public class FooController { @RequestMapping(method = RequestMethod.GET) @ResponseBody public List<Foo> findAll() { return Lists.newArrayList(new Foo(randomAlphabetic(6))); } }
-
启动访问,此时我们访问被保护的API接口会跳转到SpringSecurity内置提供的页面
http://localhost:8080/login
页面 -
我们输入账号密码后就可以正常访问,
http://localhost:8080/api/foos
,返回[{"id":0,"name":"rUNomg"}]
-
但是,当我们用user账户去访问admin账户的
http://localhost:8080/api/admin/hi
就会返回权限不足,如下提示Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Mon Nov 18 16:49:30 CST 2019 There was an unexpected error (type=Forbidden, status=403). Forbidden
二.自定义配置
1.添加统一的错误处理
-
上述出现的问题,1.访问任何页面将调到security自定义的登陆页面。2.没有访问错误做出统一的响应。3.没有对成功,失败,退出做出响应
-
定义统一的错误处理,解决无登录访问,权限不够返回,鉴权失败等的统一处理,配置如下
.exceptionHandling() .authenticationEntryPoint(restAuthenticationEntryPoint)//统一的错误处理
-
具体实现,实现AuthenticationEntryPoint类,当鉴权失败后,就会调用
commence
返回401状态码@Component public final class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence( final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
2.添加登陆成功处理
-
添加登陆成功后处理器
.formLogin() .successHandler(mySuccessHandler)
-
具体实现,自定义类实现AuthenticationSuccessHandler,重写onAuthenticationSuccess方法,执行登陆成功逻辑
@Component public class MySavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException, ServletException { //将账户信息返回 response.getWriter().write(objectMapper.writeValueAsString(authentication)); } }
3.添加登陆失败处理
-
添加登陆失败处理器
.formLogin() .failureHandler(myFailureHandler)//登陆失败处理
-
具体实现,自定义失败处理类实现AuthenticationFailureHandler,重写onAuthenticationFailure方法,执行登陆失败逻辑
@Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { //登陆失败将错误信息返回 httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e.getMessage())); } }
4.添加退出成功处理
-
添加退出成功处理器
.logout().logoutSuccessHandler(logoutSuccessHandler);//退出成功处理
-
具体实现,自定义退出成功处理类实现LogoutSuccessHandler,重写onLogoutSuccess方法,执行退出后逻辑
@Component public class MySimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler { @Autowired private ObjectMapper objectMapper; @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { //将账户信息返回 httpServletResponse.getWriter().write(objectMapper.writeValueAsString("logout success: "+authentication)); } }
5.自定义登陆账号密码key值
-
系统默认账号密码
this.usernameParameter("username"); this.passwordParameter("password");
-
我们可以通过如下配置来覆盖默认
.formLogin()//使用表单类型提交鉴权 .usernameParameter("user-name") .passwordParameter("pass-word")
6.配置类总览
-
经过上述步骤,我们看一下我们的配置类
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()//关闭csrf认证 .exceptionHandling() .authenticationEntryPoint(restAuthenticationEntryPoint)//统一的错误处理 .and() .authorizeRequests() .antMatchers("/api/foos/**").authenticated()//foos下接口进行了权限认证后就可以访问了 .antMatchers("/api/admin/**").hasRole("ADMIN")//此接口需要拥有ADMIN权限的用户才能访问 .anyRequest().authenticated()//除登陆,登出以及上述外所有请求都将拦截 .and()//连接符 .formLogin()//使用表单类型提交鉴权 //.usernameParameter("user-name") //.passwordParameter("pass-word") .successHandler(mySuccessHandler)//登陆成功处理 .failureHandler(myFailureHandler)//登陆失败处理 .and() .logout().logoutSuccessHandler(logoutSuccessHandler);//退出成功处理 }
7.验证
- SpringSecurity表单登陆默认的访问路径为
localhost:8080/login
,退出路径localhost:8080/logout
- 未授权直接访问,返回401错误
- 登陆成功返回,用户信息
三.结语
1.总结
- 使用SpringSecurity保护RestAPI,重点在
configure
方法,所有资源拦截或者放行,登陆失败处理,登陆成功处理都在此配置 - 此文章的github范例,点击获取