SpringBoot Security JJWT

SpringBoot Security JJWT

一、api开发阶段

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

image-20200902150524934

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

<dependencyManagement>
        <!--引入两个dependencies管理maven 所有包的version-->
        <dependencies>
            <dependency>
                <groupId>io.spring.platform</groupId>
                <artifactId>platform-bom</artifactId>
                <version>Brussels-SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--依赖管理-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

<build>
        <plugins>
            <!--使用maven-compiler-plugin插件可以指定项目源码的jdk版本
            ,编译后的jdk版本,以及编码-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
</build>
<!--加入子模块,子模块可使用主模块所有dependencies,
子模块只许引入自己所特需的dependencies-->
<modules>
    <module>../imooc-security-app</module>
    <module>../imooc-security-browser</module>
    <module>../imooc-security-core</module>
    <module>../imooc-security-demo</module>
</modules>

core:核心业务逻辑

<!--各种核心业务逻辑依赖,可以被其他子模块引用-->
<dependencies>
    <dependency>xxx</dependency>
    <dependency>xxx</dependency>
    ....
    <dependency>xxx</dependency>
</dependencies>

browser:浏览器安全特定代码

<!--可以直接引用其他子模块的依赖-->
<dependencies>
    <dependency>
        <groupId>com.imooc.security</groupId>
        <artifactId>imooc-security-core</artifactId>
        <version>${imooc.security.version}</version>
    </dependency>
</dependencies>

app:app相关特定代码

demo:demo程序

<!--打包可执行jar,将其他子项目下的依赖也都引入-->
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.3.3.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <finalName>demo</finalName>
</build>

2、demo API开发

3-2 增加分页查询 pagehelper

一、添加依赖

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.2</version>
</dependency>

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

#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<User> users = userService.selectAll();
        PageInfo<User> 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<User>对象无法返回再次封装对象(未解决)
    public List<User> selectAll(int pageNum,int pageSize) {
        //PageHelper.startPage(pageNum,pageSize);
        List<User> users = userService.selectAll();
        //PageInfo<User> 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<? extends Payload>[] payload() default { };
}

/**
2、校验执行类
*/
public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {
    //两个泛型。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<String> order() throws Exception {
        logger.info("主线程开始");
        String orderNumber = RandomStringUtils.randomNumeric(8);
        mockQueue.setPlaceOrder(orderNumber);
        DeferredResult<String> 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<ContextRefreshedEvent> {
    @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<String, DeferredResult<String>> map = new HashMap<String, DeferredResult<String>>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }

    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }

}

    

​3、异步处理配置

3-11 与前端协同开发

​ 使用Swagger自动生成文档

​ 使用WireMock伪造REST服务

swagger

1、添加依赖

 <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
</dependency>

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<String> 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、实现手机号+短信认证
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352