SpringBoot Security JJWT

SpringBoot Security JJWT

一、api开发阶段

1、项目构建(多模块开发)

image-20200902150524934

主模块:无代码只有pom.xml文件


        
        
            
                io.spring.platform
                platform-bom
                Brussels-SR4
                pom
                import
            
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                Dalston.SR2
                pom
                import
            
        



        
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                2.3.2
                
                    1.8
                    1.8
                    UTF-8
                
            
        



    ../imooc-security-app
    ../imooc-security-browser
    ../imooc-security-core
    ../imooc-security-demo

core:核心业务逻辑



    xxx
    xxx
    ....
    xxx

browser:浏览器安全特定代码



    
        com.imooc.security
        imooc-security-core
        ${imooc.security.version}
    

app:app相关特定代码

demo:demo程序



        
            
                org.springframework.boot
                spring-boot-maven-plugin
                1.3.3.RELEASE
                
                    
                        
                            repackage
                        
                    
                
            
        
        demo

2、demo API开发

3-2 增加分页查询 pagehelper

一、添加依赖


            com.github.pagehelper
            pagehelper-spring-boot-starter
            1.2.2

二、配置(可以不设置使用默认)

#pagehelper 分页插件
pagehelper:
  helper-dialect: mysql # 分页插件会自动检测当前的数据库链接,自动选择合适的分页方式(可以不设置)
  reasonable: true #合法性,即纠错机制,true,这时如果 pageNum <= 0 会查询第一页,如果 pageNum > pages 会查询最后一页。
  #  support-methods-arguments: true
  #  params: countSql

三、使用

    @GetMapping("/getAll")
    public ResultJson selectAll(@RequestParam(name = "pageNum",required = true,defaultValue = "1")int pageNum,@RequestParam(name = "pageSize",required = true,defaultValue = "5")int pageSize) {
        PageHelper.startPage(pageNum,pageSize); //重点
        List users = userService.selectAll();
        PageInfo pageInfo = new PageInfo<>(users);//重点
        return ResultJson.ok(pageInfo);
    }

3-3 编写用户详情服务

1、@PathVariable映射url片段到java方法的参数
2、在url申明中使用正则表达式
 @DeleteMapping("user/{id:\\d+}")//必须为数字
3、@JsonView控制json输出内容

​ 需求:不同的服务展示的字段不同,限制某些字段不显示

​ 实现步骤:

​ 1. 使用接口来声明多个视图

@Component
public class User {
    public interface UserSimpleView {};
    public interface UserDetailView extends UserSimpleView {};
    private String id;
    private String name;
    .......
}

​ 2. 在值对象的get方法上指定视图

    //因为UserDetailView继承了UserSimpleView,所以也拥有UserSimpleView的值
    @JsonView(UserDetailView.class)
    public String getPassword() {
        return password;
    }

    @JsonView(UserSimpleView.class)
    public String getLoginname() {
        return loginname;
    }

​ 3. 在Controller方法上指定视图

    @GetMapping("/getAll")
    @JsonView(User.UserDetailView.class)
    //只能返回List对象无法返回再次封装对象(未解决)
    public List selectAll(int pageNum,int pageSize) {
        //PageHelper.startPage(pageNum,pageSize);
        List users = userService.selectAll();
        //PageInfo pageInfo = new PageInfo<>(users);
        return users;
    }

3-4 处理创建请求

​ @RequestBody映射请求体到java方法的参数

​ 日期类型参数的处理(前后台传递:时间戳)

@valid注解和BindingResult验证请求参数的合法性并处理校验结果

    @PostMapping("/register")
    @ApiOperation(value = "注册")
    public ResultJson register(@Valid @RequestBody User user,BindingResult errors) {
        //使用@Valid BindingResult配合,处理校验结果
        if (errors.hasErrors()){
            errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
            return ResultJson.failure(ResultCode.BAD_REQUEST);
        }
        return userService.register(user);
    }

3-5开发用户信息修改和删除服务

​ 1、常用的验证注解
注解 含义
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
​ 2、自定义消息
    //友好提示
    @NotBlank(message = "密码不能为空")
    private String password;
​ 3、自定义校验注解
/**
1、新建一个注解类
*/
//标注的位置:方法、字段
@Target({ElementType.METHOD,ElementType.FIELD})
//运行时的注解
@Retention(RetentionPolicy.RUNTIME)
//当前注解用哪个类去校验(执行逻辑)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
    //设置校验失败信息
    String message() ;
    //可不用必须有
    Class[] groups() default { };
    //可不用必须有
    Class[] payload() default { };
}

/**
2、校验执行类
*/
public class MyConstraintValidator implements ConstraintValidator {
    //两个泛型。1、是实现哪个注解类,2、是可以放在什么类型的字段上
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        System.out.println("注解初始化");
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        System.out.println("打印值:"+value);
        return true;//返回true or false 是否校验成功
    }
}

3-6 RESTful API错误处理

​ Spring Boot中默认的错误处理机制

​ 自定义异常处理(默认基本够用)


3-7 RESTful API的拦截

过滤器 Filter(无法获取所调用方法的名称、参数,可以获取request,response)

//1 编写一个过滤器实现(implements)Filter接口

//2 分别实现init、doFilter(chain.doFilter(request,response))、destroy方法

//3 注册:将Filter配置到项目。方法一:在Filter类上添加@Component.方法二:添加WebConfig配置类

拦截器 Interceptor(无法获取所调用方法的参数,可以获取request,response,所调用方法名称)

// 1 编写一个拦截器实现(implements)HandlerInterceptor接口

//  2 分别实现 

    preHandle (通过request.setAttribute传递信息给postHandle)

    postHandle(看preHandle是否报错决定是否执行)

    afterCompletion(始终执行)

//  3 声明@Component注解

//  4 注册:写配置类继承WebMvcConfigurerAdapter
@Configuration
public class AddInterceptor extends WebMvcConfigurerAdapter{
    
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(new MyInterceptor())
        .addPathPatterns("/**")
        .excludePathPatterns("/login")
        .excludePathPatterns("/loginPost");
    }
}

切片Aspect(无法获取request,response,可以获取所调用方法参数)

//编写切片,定义切片,定义切入点
@Aspect
@Component
public class MyAspect {//这就是一个切片
    /**
     * 定义切入点,切入点为com.example.demo.aop.AopController中的所有函数
     * 通过@Pointcut注解声明频繁使用的切点表达式
     **/
    @Around("execution(* cn.com.controll.UserController.*(..)))")//这是一个切入点,下面的方法就是增强
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        long start =new Date().getTime();
        //打印参数
        System.out.println("method before execute,aspect start.now time is:"+start);
        Object[] args = pjp.getArgs();
        for (Object arg:args) {
            System.out.println("arg is :"+arg.toString());
        }
        Object proceed = pjp.proceed();//执行方法
        System.out.println("method after execute,aspect end.spend time:"+(new Date().getTime()-start));
        return proceed;
    }
}

3-8 Spring AOP

image-20200902162208407
image-20200902162228981

3-10 使用多线程提高REST服务性能

1、使用Runnable异步处理Rest服务

只能由主线程调用副线程,无法满足多服务器复杂要求

image-20200902162324113
2、使用DeferredResult异步处理Rest服务
image-20200902162356635

对象:接收请求的应用1、处理逻辑的应用2

过程:1.2.3-> 应用1(线程1)接收请求发送至消息队列,应用2(线程3)监听消息队列的变化进行处理。

​ 4.5.6-> 处理完后发送至消息队列,应用1(线程2)监听消息队列,获取结果,进行响应。

实现过程(模拟,未实现应用2):

//1. 主线程
@RestController
public class AsyncController {
    @Autowired
    private MockQueue mockQueue;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    private Logger logger = LoggerFactory.getLogger(getClass());
    @RequestMapping("/order")
    public DeferredResult order() throws Exception {
        logger.info("主线程开始");
        String orderNumber = RandomStringUtils.randomNumeric(8);
        mockQueue.setPlaceOrder(orderNumber);
        DeferredResult result = new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNumber, result);
        logger.info("主线程结束");
        return result;
    }
}

//2. 消息队列
@Component
public class MockQueue {
    private String placeOrder;
    private String completeOrder;
    private Logger logger = LoggerFactory.getLogger(getClass());
    public String getPlaceOrder() {
        return placeOrder;
    }
    public void setPlaceOrder(String placeOrder) throws Exception {
        new Thread(() -> {
            logger.info("接到下单请求, " + placeOrder);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;
            logger.info("下单请求处理完毕," + placeOrder);
        }).start();
    }
    public String getCompleteOrder() {

        return completeOrder;
    }
    public void setCompleteOrder(String completeOrder) {

        this.completeOrder = completeOrder;
    }
}

//3. 监听器
@Component
public class QueueListener implements ApplicationListener {
    @Autowired
    private MockQueue mockQueue;
    @Autowired
    private DeferredResultHolder deferredResultHolder;
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        new Thread(() -> {
            while (true) {
                if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
                    String orderNumber = mockQueue.getCompleteOrder();
                    logger.info("返回订单处理结果:"+orderNumber);
                    deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
                    mockQueue.setCompleteOrder(null);
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}


//4. DeferredResultHolder(应用1中线程1、2通信)
@Component
public class DeferredResultHolder {

    private Map> map = new HashMap>();

    public Map> getMap() {
        return map;
    }

    public void setMap(Map> map) {
        this.map = map;
    }

}

    

​3、异步处理配置

3-11 与前端协同开发

​ 使用Swagger自动生成文档

​ 使用WireMock伪造REST服务

swagger

1、添加依赖

 
            io.springfox
            springfox-swagger2
            2.7.0
        
        
            io.springfox
            springfox-swagger-ui
            2.7.0

2、新增配置类

@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/");

        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }
}

3、在启动类上添加注释

@EnableSwagger2

4、在controller和domain上添加注释

@ApiOperation(value = "注册")//方法
@ApiParam("用户ID")//路径参数
@ApiModelProperty(value = "登陆密码")//domain对象
WireMock

1、下载 wiremock-standalone-2.27.0.jar 包

2、开启wiremock服务

java -jar wiremock-standalone-2.27.0.jar --port 9999

3、创建假数据文件,并发送到服务

public class MockServer {
    public static void main(String[] args) throws IOException {
        WireMock.configureFor(9999);
        WireMock.removeAllMappings();
        mock("/mock/response/01.txt","/order/1");
        mock("/mock/response/02.txt","/order/2");
    }
    public static void mock(String ResourceUrl, String ApiUrl) throws IOException {
        ClassPathResource classPathResource = new ClassPathResource(ResourceUrl);
        File file = classPathResource.getFile();
        List strings = FileUtils.readLines(file, "UTF-8");
        String content = StringUtils.join(strings.toArray(),"\n");
        WireMock.stubFor(WireMock.get(WireMock.urlPathEqualTo(ApiUrl))
                                 .willReturn(WireMock.aResponse()
                                 .withBody(content)
                                 .withStatus(200)));
    }
}

二、SpringSecurity认证、授权阶段

1、SpringSecurity基本原理
image-20200907101341325

1.1 ExceptionTranslationFilter:FilterSecurityInterceptor未通过时的异常处理

1.2 FilterSecurityInterceptor:根据配置类中的配置来判断是否通过

1.3 自定义用户认证逻辑

//1、处理用户信息获取逻辑(UserDetailsService:是否有用户)
​   重点(UserDetailsService接口):有唯一根据用户名查询用户方法。根据username返回用户信息,信息被封装于(UserDetails接口)的实现类中,再根据UserDetails用户信息去校验。

​   过程:1.实现 UserDetailsService,根据用户名查询用户信息;
         
//2、处理用户校验逻辑(UserDetails:是否过期)
普通domain类实现UserDetails。

//3、处理密码加密解密(BCryptPasswordEncoder)
​   加密:String encode = new BCryptPasswordEncoder().encode("123456");
​   密码匹配:new BCryptPasswordEncoder().matches();

1.4 个性化用户认证流程

​ 1.4 .1自定义登陆页面

//1 编写配置文件
http.formLogin()
     .loginPage("/imooc-signIn.html")//配置登陆页面
     .and()
     .authorizeRequests()
      //登陆页面无需身份认证
     .antMatchers("/imooc-signIn.html").permitAll()
     .anyRequest().authenticated();
//2 在resources下增加登陆页面

​ 1.4 .2自定义登陆成功处理

image-20200907150116751
 .and()
               .formLogin().loginPage("/login_p").loginProcessingUrl("/login").permitAll()

​ 1.4 .3自定义登陆失败处理

2、实现用户名+密码认证
3、实现手机号+短信认证

你可能感兴趣的:(SpringBoot Security JJWT)