SpringSecurity保护RestAPI

  • A+
所属分类:Java Spring SpringSecurity

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错误
    SpringSecurity保护RestAPI
  • 登陆成功返回,用户信息
    SpringSecurity保护RestAPI

三.结语

1.总结

zhangfeng

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: