• springboot学习(七)安全管理 spring security


    Spring Security入门

    Spring Security 是 Spring 家族中的一个安全管理框架,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security

    • 添加依赖,只要加入依赖,项目的所有接口都会被自动保护起来,访问系统会先需要登录认证

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    默认的用户名为user,密码在控制台,每次启动项目都会变,对登录的用户名/密码进行配置,有三种不同的方式:

    1. 在 application.properties 中进行配置

    2. 通过 Java 代码配置在内存中

    3. 通过 Java 从数据库中加载

    • 用户名密码配置

      • 在 application.properties 中配置

      spring.security.user.name=hjy
      spring.security.user.password=123456

      配置完成后,重启项目,就可以使用这里配置的用户名/密码登录了

      • 在Java代码中配置

      创建一个 Spring Security 的配置类,继承自 WebSecurityConfigurerAdapter 类,使用BCryptPasswordEncoder 进行密码加密(从 Spring5 开始,强制要求密码要加密)

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
             //配置两个用户,密码都加密
             auth.inMemoryAuthentication() .withUser("hjy1").roles("admin").password("$2a$10$D5GuLLF.OOzP28g9Xy1FKu82dj044JeFsNLpujm8sM7xti4IWCTju")
                    .and()
      .withUser("hjy2").roles("user").password("$2a$10$4HMMAfpD0xkwq15ceMY4/OZtlETHvLGhCJox3O1Cn9XmsqAUTLxZq");
        }
         @Bean
         PasswordEncoder passwordEncoder(){
             return new BCryptPasswordEncoder();
        }
      }

      Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存 盐的字段了

      • 登录配置

      对于登录接口,登录成功后的响应,登录失败后的响应,我们都可以在 WebSecurityConfigurerAdapter 的实现类中进行配置

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter{
      @Autowired
         VerifyCodeFilter verifyCodeFilter;
      @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
             http.authorizeRequests()//开启登录配置
                .antMatchers("/hello").hasRole("admin")//表示访问 /hello 这个接口,需要具备 admin 这个角色
                .anyRequest().authenticated()//表示剩余的其他接口,登录之后就能访问
                .and()
      .formLogin()
                .loginPage("/login_p")//定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
                .loginProcessingUrl("/doLogin")//登录处理接口
                .usernameParameter("uname")//定义登录时,用户名的 key,默认为 username
                .passwordParameter("passwd")//定义登录时,用户密码的 key,默认为 password
                .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();
      out.write("success");
      out.flush();
      }
      })
      .failureHandler(new AuthenticationFailureHandler(){
      @Override
      public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException,ServletException{
      resp.setContentType("application/json;charset=utf-8");
      PrintWriter out = resp.getWriter();
      out.write("fail");
      out.flush();
      }
      })
      .permitAll()//和表单登录相关的接口统统都直接通过
                .and()
      .logout()
      .logoutUrl("/logout")
      .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();
      out.write("logout success");
      out.flush();
      }
      })
      .permitAll()
      .and()
      .httpBasic()
      .and()
      .csrf()
      .disable();
        }
      }

      VerifyCodeFilter是自定义的图片验证码,参考图片验证码

      可以在 successHandler 方法中,配置登录成功的回调,如果是前后端分离开发的话,登录成功后返回 JSON 即可,同理,failureHandler 方法中配置登录失败的回调,logoutSuccessHandler 中则配置注销成功的回调

      • 忽略拦截

      如果某一个请求地址不需要拦截的话,过滤掉该地址,即该地址不走 Spring Security 过滤器链

      @Configuration
      public class SecurityConfig extends WebSecurityConfigurerAdapter{
      @Override
         public void configure(WebSecurity web) throws Exception{
             web.ignoring().antMatchers("/vercode");
        }
      }

    Spring Security添加验证码

    • 验证码工具类

    public class VerifyCode {
       private int width = 100;// 生成验证码图片的宽度
       private int height = 50;// 生成验证码图片的高度
       private String[] fontNames = {"宋体", "楷体", "隶书", "微软雅黑"};
       private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色
       private Random random = new Random();
       private String codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
       private String text;// 记录随机字符串
       /**
        * 获取一个随意颜色
        * @return
        */
       private Color randomColor() {
           int red = random.nextInt(150);
           int green = random.nextInt(150);
           int blue = random.nextInt(150);
           return new Color(red, green, blue);
      }
       /**
        * 获取一个随机字体
        * @return
        */
       private Font randomFont() {
           String name = fontNames[random.nextInt(fontNames.length)];
           int style = random.nextInt(4);
           int size = random.nextInt(5) + 24;
           return new Font(name, style, size);
      }
       /**
        * 获取一个随机字符
        * @return
        */
       private char randomChar() {
           return codes.charAt(random.nextInt(codes.length()));
      }
       /**
        * 创建一个空白的BufferedImage对象
        * @return
        */
       private BufferedImage createImage() {
           BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
           Graphics2D g2 = (Graphics2D) image.getGraphics();
           g2.setColor(bgColor);// 设置验证码图片的背景颜色
           g2.fillRect(0, 0, width, height);
           return image;
      }
       public BufferedImage getImage() {
           BufferedImage image = createImage();
           Graphics2D g2 = (Graphics2D) image.getGraphics();
           StringBuffer sb = new StringBuffer();
           for(int i = 0; i < 4; i++) {
               String s = randomChar() + "";
               sb.append(s);
               g2.setColor(randomColor());
               g2.setFont(randomFont());
               float x = i * width * 1.0f / 4;
               g2.drawString(s, x, height - 15);
          }
           this.text = sb.toString();
           drawLine(image);
           return image;
      }
       /**
        * 绘制干扰线
        * @param image
        */
       private void drawLine(BufferedImage image) {
           Graphics2D g2 = (Graphics2D) image.getGraphics();
           int num = 5;
           for(int i = 0; i < num; i++) {
               int x1 = random.nextInt(width);
               int y1 = random.nextInt(height);
               int x2 = random.nextInt(width);
               int y2 = random.nextInt(height);
               g2.setColor(randomColor());
               g2.setStroke(new BasicStroke(1.5f));
               g2.drawLine(x1, y1, x2, y2);
          }
      }
       public String getText() {
           return text;
      }
       public static void output(BufferedImage image, OutputStream out) throws IOException {
           ImageIO.write(image, "JPEG", out);
      }
    }
    • 验证码controller

    @RestController
    public class VerifyController {
       @GetMapping("/verifyCode")
       public void code(HttpServletRequest request, HttpServletResponse response) throws IOException {
           VerifyCode code=new VerifyCode();
           BufferedImage image=code.getImage();
           String text=code.getText();
           HttpSession session=request.getSession();
           session.setAttribute("index_code",text);
           VerifyCode.output(image,response.getOutputStream());
      }
    }

    创建了一个VerifyCode对象,将生成的验证码字符保存到session中,然后通过流将图片写到前端,img标签如下

    <img src="/verifyCode" alt="">
    • 自定义过滤器

    @Component
    public class VerifyCodeFilter extends GenericFilterBean {
       private String defaultFilterProcessUrl="/doLogin";

       @Override
       public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
           HttpServletRequest request=(HttpServletRequest)servletRequest;
           HttpServletResponse response=(HttpServletResponse)servletResponse;
           if("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())){
               //验证码验证
               String requestCaptcha=request.getParameter("code");
               String genCaptcha=(String)request.getSession().getAttribute("index_code");
               if(StringUtils.isEmpty(requestCaptcha)){
                   throw new AuthenticationException("验证码不能为空");
              }
               if(!genCaptcha.equalsIgnoreCase(requestCaptcha)){
                   throw new AuthenticationException("验证码错误");
              }
           filterChain.doFilter(request,response);
          }
      }
    }

    自定义过滤器继承自GenericFilterBean,并实现其中的doFilter方法,在doFilter方法中,当请求方法是POST,并且请求地址是 /doLogin时,获取参数中的code字段值,该字段保存了用户从前端页面传来的验证码,然后获取session中保存的验证码,如果用户没有传来验证码,则抛出验证码不能为空异常,如果用户传入了验证码,则判断验证码是否正确,如果不正确则抛出异常,否则执行 chain.doFilter(request,response);使请求继续向下走

    • 在WebSecurityConfigurerAdapter 的实现类中进行配置,见入门登录配置

    Spring Security登录使用json

    通过分析源码我们发现,默认的用户名密码提取在UsernamePasswordAuthenticationFilter过滤器中,如果想将用户名密码通过JSON的方式进行传递,则需要自定义相关过滤器将其替换即可

    自定义过滤器:将用户名/密码的获取方案重新修正下,改为了从JSON中获取用户名密码

    public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
       @Override
       public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
       if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
           ObjectMapper mapper = new ObjectMapper();
           UsernamePasswordAuthenticationToken authRequest = null;
           try(InputStream is = request.getInputStream()) {
               Map < String, String > authenticationBean = mapper.readValue(is, Map.class);
               authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.get("username"), authenticationBean.get("password"));
          } catch(IOException e)
          { e.printStackTrace();
               authRequest = new UsernamePasswordAuthenticationToken("", "");
          } finally
          { setDetails(request, authRequest);
               return this.getAuthenticationManager().authenticate(authRequest);
          }
            } else {
           return super.attemptAuthentication(request, response);
            }
      }
    }

    在SecurityConfig(WebSecurityConfigurerAdapter 实现类)中,将自定义的CustomAuthenticationFilter类加入进来即可

        @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests()
              .anyRequest()
              .authenticated()
              .and()
              .formLogin()
              .and()
              .csrf()
              .disable();
           http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
      }
       @Bean
       CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
           CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
           filter.setAuthenticationSuccessHandler(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();
                   RespBean respBean = RespBean.ok("登录成功!");
                   out.write(new ObjectMapper().writeValueAsString(respBean));
                   out.flush();
                   out.close();
              }
          });
           filter.setAuthenticationFailureHandler(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();
                   RespBean respBean = RespBean.error("登录失败!");
                   out.write(new ObjectMapper().writeValueAsString(respBean));
                   out.flush();
                   out.close();
              }
          });
           filter.setAuthenticationManager(authenticationManagerBean());
           return filter;
      }

    SpringSecurity中的角色继承

    角色继承实际上是一个很常见的需求,因为大部分公司治理可能都是金字塔形的,上司可能具备下属的部分甚至所有权限,这一现实场景,反映到我们的代码中,就是角色继承了,角色继承关系的解析在RoleHierarchyImpl类的buildRolesReachableInOneStepMap方法中

    配置角色的继承关系:

        @Bean
       RoleHierarchy roleHierarchy() {
           RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
           String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";
           roleHierarchy.setHierarchy(hierarchy);
           return roleHierarchy;
      }

    提供了一个RoleHierarchy接口的实例,使用字符串来描述了角色之间的继承关系, ROLE_dba具备 ROLE_admin的所有权限,而 ROLE_admin则具备 ROLE_user的所有权限,继承与继承之间用一个换行符隔开。提供了这个Bean之后,以后所有具备 ROLE_user角色才能访问的资源, ROLE_dbaROLE_admin也都能访问,具备 ROLE_amdin角色才能访问的资源, ROLE_dba也能访问

    在SecurityConfig(WebSecurityConfigurerAdapter 实现类)中指定角色和资源的对应关系即可

        @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests()
              .antMatchers("/admin/**")
              .hasRole("admin")
              .antMatchers("/db/**")
              .hasRole("dba")
              .antMatchers("/user/**")
              .hasRole("user")
              .and()
              .formLogin()
              .loginProcessingUrl("/doLogin")
              .permitAll()
              .and()
              .csrf()
              .disable();
      }

    这个表示 /db/**格式的路径需要具备dba角色才能访问, /admin/**格式的路径则需要具备admin角色才能访问, /user/**格式的路径,则需要具备user角色才能访问,此时提供相关接口,会发现,dba除了访问 /db/**,也能访问 /admin/**/user/**,admin角色除了访问 /admin/**,也能访问 /user/**,user角色则只能访问 /user/**

    SpringSecurity中使用JWT

    • 无状态是什么

      • 微服务集群中的每个服务,对外提供的都使用RESTful风格的接口。而RESTful风格的一个最重要的规范就是:服务的无状态性,即:

        • 服务端不保存任何客户端请求者信息

        • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

        那么这种无状态性有哪些好处呢?

        • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器

        • 服务端的集群和状态对客户端透明

        • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)

        • 减小服务端存储压力

    • 无状态登录流程

      • 首先客户端发送账户名/密码到服务端进行认证

      • 认证通过后,服务端将用户信息加密并且编码成一个token,返回给客户端

      • 以后客户端每次发送请求,都需要携带认证的token

      • 服务端对客户端发送来的token进行解密,判断是否有效,并且获取用户登录信息

    JWT,全称是Json Web Token, 是一种JSON风格的轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权

    JWT包含三部分数据:

    1.Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT

    • 加密算法,自定义

    我们会对头部进行Base64Url编码(可解码),得到第一部分数据。

    2.Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息:

    • iss (issuer):表示签发人

    • exp (expiration time):表示token过期时间

    • sub (subject):主题

    • aud (audience):受众

    • nbf (Not Before):生效时间

    • iat (Issued At):签发时间

    • jti (JWT ID):编号

    这部分也会采用Base64Url编码,得到第二部分数据。

    3.Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过Header中配置的加密算法生成。用于验证整个数据完整和可靠性。

    生成的数据格式如下图:

    img

    注意,这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已。

    因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了RESTful的无状态规范。

    JWT的问题

    1. 续签问题,这是被很多人诟病的问题之一,传统的cookie+session的方案天然的支持续签,但是jwt由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入redis,虽然可以解决问题,但是jwt也变得不伦不类了。

    2. 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改secret来实现注销,服务端secret修改后,已经颁发的未过期的token就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。

    3. 密码重置,密码重置后,原本的token依然可以访问系统,这时候也需要强制修改secret。

    4. 基于第2点和第3点,一般建议不同用户取不同secret。

    • 添加依赖

            <dependency>
               <groupId>io.jsonwebtoken</groupId>
               <artifactId>jjwt</artifactId>
               <version>0.9.1</version>
           </dependency>
    • 创建User类实现UserDetails 接口

    public class User implements UserDetails {
       private String username;
       private String password;
       private List < GrantedAuthority > authorities;
       public String getUsername() {
           return username;
      }
       @Override
       public boolean isAccountNonExpired() {
           return true;
      }
       @Override
       public boolean isAccountNonLocked() {
           return true;
      }
       @Override
       public boolean isCredentialsNonExpired() {
           return true;
      }
       @Override
       public boolean isEnabled() {
           return true;
      }
       //省略getter/setter
    }
    • JMT过滤器配置

    提供两个和 JWT 相关的过滤器配置:

    1. 一个是用户登录的过滤器,在用户的登录的过滤器中校验用户是否登录成功,如果登录成功,则生成一个token返回给客户端,登录失败则给前端一个登录失败的提示。

    2. 第二个过滤器则是当其他请求发送来,校验token的过滤器,如果校验成功,就让请求继续执行

      • 登录过滤器

      public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
         protected JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
             super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
             setAuthenticationManager(authenticationManager);
        }
         @Override
         public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {
             User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
             return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
        }
         @Override
         protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse resp, FilterChain chain, Authentication authResult) throws IOException, ServletException {
             Collection <? extends GrantedAuthority > authorities = authResult.getAuthorities();
             StringBuffer as = new StringBuffer();
             for(GrantedAuthority authority: authorities) {
                 as.append(authority.getAuthority()).append(",");
            }
             String jwt = Jwts.builder()
                            .claim("authorities", as)//配置用户角色
                            .setSubject(authResult.getName())
                            .setExpiration(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
                            .signWith(SignatureAlgorithm.HS512, "sang@123")
                            .compact();
             resp.setContentType("application/json;charset=utf-8");
             PrintWriter out = resp.getWriter();
             out.write(new ObjectMapper().writeValueAsString(jwt));
             out.flush();
             out.close();
        }
             protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse resp, AuthenticationException failed) throws IOException, ServletException {
                 resp.setContentType("application/json;charset=utf-8");
                 PrintWriter out = resp.getWriter();
                 out.write("登录失败!");
                 out.flush();
                 out.close();
            }
      }

      解析:

      1. 自定义 JwtLoginFilter 继承自 AbstractAuthenticationProcessingFilter,并实现其中的三个默认方法。

      2. attemptAuthentication方法中,我们从登录参数中提取出用户名密码,然后调用AuthenticationManager.authenticate()方法去进行自动校验。

      3. 第二步如果校验成功,就会来到successfulAuthentication回调中,在successfulAuthentication方法中,将用户角色遍历然后用一个 , 连接起来,然后再利用Jwts去生成token,按照代码的顺序,生成过程一共配置了四个参数,分别是用户角色、主题、过期时间以及加密算法和密钥,然后将生成的token写出到客户端。

      4. 第二步如果校验失败就会来到unsuccessfulAuthentication方法中,在这个方法中返回一个错误提示给客户端即可。

      • token校验过滤器

      public class JwtFilter extends GenericFilterBean {
         @Override
         public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
             HttpServletRequest req = (HttpServletRequest) servletRequest;
             String jwtToken = req.getHeader("authorization");
             System.out.println(jwtToken);
             Claims claims = Jwts.parser().setSigningKey("sang@123").parseClaimsJws(jwtToken.replace("Bearer", "")).getBody();
             String username = claims.getSubject();
             //获取当前登录用户名
             List < GrantedAuthority > authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
             UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);
             SecurityContextHolder.getContext().setAuthentication(token);
             filterChain.doFilter(req, servletResponse);
        }
      }

      解析:

      1. 首先从请求头中提取出 authorization 字段,这个字段对应的value就是用户的token。

      2. 将提取出来的token字符串转换为一个Claims对象,再从Claims对象中提取出当前用户名和用户角色,创建一个UsernamePasswordAuthenticationToken放到当前的Context中,然后执行过滤链使请求继续执行下去。

      • Spring Security 配置

      @Configuration
      public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         @Bean
         PasswordEncoder passwordEncoder() {
             return NoOpPasswordEncoder.getInstance();
        }
         @Override
         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
             auth.inMemoryAuthentication()
                .withUser("admin")
                .password("123")
                .roles("admin")
                .and()
                .withUser("sang")
                .password("456")
                .roles("user");
        }
         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.authorizeRequests()
                .antMatchers("/hello")
                .hasRole("user")
                .antMatchers("/admin")
                .hasRole("admin")
                .antMatchers(HttpMethod.POST, "/login")
                .permitAll().anyRequest()
                .authenticated()
                .and()
                .addFilterBefore(new JwtLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf()
                .disable();
        }
      }

      解析:

      1. 简单起见,这里我并未对密码进行加密,因此配置了NoOpPasswordEncoder的实例。

      2. 简单起见,这里并未连接数据库,我直接在内存中配置了两个用户,两个用户具备不同的角色。

      3. 配置路径规则时, /hello 接口必须要具备 user 角色才能访问, /admin 接口必须要具备 admin 角色才能访问,POST 请求并且是 /login 接口则可以直接通过,其他接口必须认证后才能访问。

      4. 最后配置上两个自定义的过滤器并且关闭掉csrf保护。

  • 相关阅读:
    为了快一点为什么却要慢一点
    大数与小数的求和算法
    Tips for newbie to read source code
    学习Data Science/Deep Learning的一些材料
    Git Sophisticated Commands
    两套JRE
    Java environment variables and their functionality
    Change Git Default Editor in Windows
    Multiton & Singleton
    Java Synchronized Blocks
  • 原文地址:https://www.cnblogs.com/yjh1995/p/14164446.html
Copyright © 2020-2023  润新知