springSecurity2的自定义账号密码认证流程+多个参数的自定义流程

声明,本文章是学习 b站三更草堂的视频的时候所写

依赖

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

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

        <!--security 的依赖...........开始-->
        <dependency>  <!--引入依赖之后,重启项目 访问接口就变了 需要登录,用户名是 user 密码看控制台  当然后期都可以修改-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--security 的依赖...........结束-->

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!--jwt 的 java依赖 简称 jjwt.......开始-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!--jwt 的 java依赖 简称 jjwt.......结束-->

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

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.25</version>
        </dependency>

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

    </dependencies>

配置

spring:
  application:
    name: demo
  redis:
    host: localhost
    port: 6379
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetServer=utf8mb4&useTimezone=true&serverTimezone=Hongkong
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8081

测试项目是否正常

@RestController
@RequestMapping("/security")
public class MyController {

    @GetMapping("/hello")
    public String sayHello() {
        return "hello";
    }
}

实体类

这里我使用的是 MyBatisX插件生成的
这里我打算模拟学生试用账号密码登录学生系统,结构如下
在这里插入图片描述
这里的 studentName是唯一的

概述

在加上了 security之后,访问任何资源就会被保护起来,只有登录之后才能继续访问。而采用的登陆方式是:账号密码登录
账号是 :user
密码是 :控制台输出的

而整个 security 可以看成是一个大型的过滤器链
一共有 15个过滤器,其中,最重要的过滤器就是校验账号密码的过滤器,也就是第6个: UsernamePasswordAuthenqticationFilter
在这里插入图片描述
从这里我们就可以看到 我们输入的账号密码是被5号过滤器校验的,以及登陆页面,登出界面是被6和7号过滤器加载的,13号过滤器是处理security期间的异常,14号过滤器是用来校验权限的。
那么我们重点就来修改 5号过滤器

UsernamePasswordAuthenqticationFilter

我们从使用上基本可以猜出,UsernamePasswordAuthenqticationFilter这个过滤器中做的就是,账号密码的校验,但是其中具体是怎么实现的?
在这里插入图片描述
这里只是UsernamePasswordAuthenqticationFilter这一个过滤器所需要经过的流程

但是,重点来了

security是前后端不分离的,使用的是session,和我们现在所使用的 前后端分离所需要的token,是不搭边的。
但是,UsernamePasswordAuthenqticationFilter所用到的 AuthenticationManager+xxxProvider++xxxService 很好用,包括了密码校验,权限的添加。所以!!我们使用 controller来接收学生所输入的账号和密码,直接调用 AuthenticationManager+xxxProvider++xxxService,拿取返回值,有返回值代表 认证成功,然后我们自己去生成token返回给前端

形象一些来说,security中,有很多过滤器,每一个过滤器又会调用很多其他的函数,这些,我们 都不用,我们只看上了 AuthenticationManager+xxxProvider++xxxService这个流程。所以我们只需要使用 AuthenticationManager+xxxProvider++xxxService即可,就是从 整个security中,单独的将 AuthenticationManager+xxxProvider++xxxService这套流程 拽出来,供我们使用,取其精华。

而,AuthenticationManager+xxxProvider++xxxService这一整套流程,入口就是 AuthenticationManager.authencate();所以我们只需要 拿到 AuthenticationManager,然后弄出一个符合入参的对象,直接调用即可

另外,我们应该还发现了,密码是从 控制台拿到的,账号固定是user,和我们的数据库完全没有关系,所以还需要和我们的数据库产生一些联系

这个联系是在 xxxService中实现的
所以:
AuthenticationManager是入口
xxxProvider是密码的校验
xxxService是从数据库中查数据

具体实现

  1. 自己写一个 controller 来接收 账号密码
@PostMapping("/loginWith2Param")
    public Object login(@RequestBody StudentLoginModel model) {
        return null;
    }
  1. 我们需要拿到 AuthenticationManager,所以首先是将这个对象注入到容器
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean   /*将 AuthenticationManager注入容器*/
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

然后在需要的地方注入这个对象并调用方法,我是在controller里面注入的(这里也是为了方便理解,不再去调用 xxxService,然后再去写 xxxServiceImpl ,来回跳转看着麻烦)
在这里插入图片描述
注入之后,我们就可以使用它了,里面就一个方法

authenticationManager.authenticate();

问题来了,需要的是一个 Authentication对象,但是我们现在只有 账号和密码,所以下一步就是,利用账号和密码封装一个Authentication的对象。
在这里插入图片描述
这里我们使用的是 框架自带的 UsernamePasswordAuthenticationToken;
像图片中这样创建出来就可以了;

  1. 然后 还需要写一个根据username查询数据库的方法
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    /*根据用户名查找用户+权限的赋值*/

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    }
}

发现需要返回的是一个 UserDetails(是个接口)类型对象,没有,怎么办(其实框架是自带了一个User类,但是不好用,所以基本都是自己写)。我们需要自己创建一个,毕竟接口就是用来实现的

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyUserDetails implements UserDetails {

    /*todo-6 实现UserDetails接口,完善里面的方法*/
    private Student student;	//这就是我们从数据库中查出来的对象

    private Collection<? extends GrantedAuthority> authorities;	//这个是权限集合

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return student.getPassword();
    }

    @Override
    public String getUsername() {
        return student.getStudentName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

现在,有了 UserDetails 类型的对象,只需要将这个对象创建出来然后返回即可

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    /*根据用户名查找用户+权限的赋值*/
    @Autowired
    private StudentMapper studentMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<Student>().eq(Student::getStudentName, username);
        Student student = studentMapper.selectOne(wrapper);

        /*权限--这里其实也是从数据库查出来的 这里做省略 主要是要建立一个符合 rbac 的数据库 麻烦*/
        ArrayList<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        simpleGrantedAuthorities.add(new SimpleGrantedAuthority("super"));
        simpleGrantedAuthorities.add(new SimpleGrantedAuthority("user"));
        simpleGrantedAuthorities.add(new SimpleGrantedAuthority("bandTestQuery"));//等级考试查询权限

        /*封装成UserDetails*/
        /*todo-5 这里需要返回 UserDetails类型的对象,但是没有,所以需要新建一个类来实现*/
        MyUserDetails myUserDetails = new MyUserDetails(student, simpleGrantedAuthorities);
        return myUserDetails;
    }
}
  1. 至此, 我们成功调用 authenticationManager.authenticate 方法,剩下的执行流程就是框架帮我们实现即可,我是这么写的
 @PostMapping("/loginWith2Param")
    public Object login(@RequestBody StudentLoginModel model) {

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(model.getUsername(),model.getPassword());

        /*todo-3 拿到这个对象,就去调用方法,也就是整个认证流程的入口*/
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

//以下的代码只是为了展示 认证成功后 authenticate 有什么,长什么样子
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("authenticate.getDetails()", authenticate.getDetails());
        hashMap.put("authenticate.getPrincipal()", authenticate.getPrincipal());/*就是自己定义的 UserDetails实现类*/
        hashMap.put("authenticate.getCredentials()", authenticate.getCredentials());
        hashMap.put("authenticate.getAuthorities()", authenticate.getAuthorities());/*就是权限列表*/

//下面是根据 学生id 生成token 其实token就是一个字符串,可以根据自己的喜好来自定义生成,但是为了安全,所以基本都是采用加密的形式生成的,所以我们生成的token还需要能自己解密出来
        MyUserDetails userDetails = (MyUserDetails) authenticate.getPrincipal();
        JwtBuilder jwtBuilder = Jwts.builder()
                .setId("stem86")//id
                .setSubject("studentToken")//主题
                .setIssuedAt(new Date(System.currentTimeMillis()))//签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24))//过期时间
                .claim("userId", userDetails.getStudent().getId())//设置一些信息
                .signWith(SignatureAlgorithm.HS256, "jiubodou");//加密方式 + 密钥(不能泄露)
        /*todo-7 authenticationManager.authenticate结束后,就可以根据学生id生成token */
        String token = jwtBuilder.compact();

//下面是解密
        Claims jiubodou = Jwts.parser().setSigningKey("jiubodou").parseClaimsJws(token).getBody();
        Integer userId = (Integer) jiubodou.get("userId");	//这里的 userId是解密出来的userId,应该和加密之前的userId一样
        hashMap.put("token", token);
        hashMap.put("userId", userDetails.getStudent().getId());
        stringRedisTemplate.opsForValue().set("student:" + userId, JSON.toJSONString(userDetails));//存入redis
        return hashMap;
    }
  1. 至此,权限的认证已经全部完成,那么权限认证已经全部完成了,剩下的就是用户带着token来访问需要权限的才能访问的接口了。那么就是说,我们需要从 token 中解析出:这个用户是谁,他有什么权限,每一个请求都需要这么做,所以我们采用 过滤器来做这件事
@Component
public class MyJwtTokenFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    /*todo-10 过滤器,所有的请求都会经过过滤器,只不过有些请求会直接放行,就比如 /login 这里需要解析token并封装对象,
       不做这一步的话,那么请求接口的人是谁,登陆没有?有哪些权限都不知道*/
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        /*解析token,当然这里要和前端商量好,token需要放在 header 中 */
        String token = request.getHeader("token");/*例如:token:eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJzdGVtODYiLCJzdWIiOiJzdHVkZW50VG9rZW4iLCJpYXQiOjE2ODE5NTk4NDMsImV4cCI6MTY4MTk1OTg0OCwidXNlcklkIjoxfQ.yX7RHqWLsTFgWCjgycsp7NSjPZlXu89bbfm8KcAuIrQ*/

        if (StringUtils.isBlank(token)) {
            filterChain.doFilter(request, response);
            return;
        }

        Claims jiubodou = Jwts.parser().setSigningKey("jiubodou").parseClaimsJws(token).getBody();
        Integer userId = (Integer) jiubodou.get("userId");//解析出是谁

        /*根据userId查用户信息*/
        String s = stringRedisTemplate.opsForValue().get("student:" + userId);
        UserDetails userDetails = JSON.parseObject(s, UserDetails.class);
        if (Objects.isNull(userDetails)) {
            filterChain.doFilter(request, response);
            return;
        }
        /*重点,从 redis中拿到用户信息了,将其封装起来*/
        Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userDetails, null, authorities);

//设置到 security 的上下文,因为:这个用户有哪些权限我们这里是设置好了,但是访问接口的时候,权限得到对比我们不做,是由 security来做的,那么也就是说 security是需要某个对象的,拿到这个对象所拥有的权限和接口需要的权限做对比。而这个对象就是 authentication ,所以我们需要将这个对象设置到 security的上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}
  1. 来访问接口试试
    @GetMapping("/getStudent")/*todo-9 成功登录之后,来访问需要权限的接口*/
    @PreAuthorize(" @ms.check('bandTestQuery')  ")  /*todo-11 有了过滤器之后,才能够直接拿到访问的人是谁 有没有权限*/
    public HashMap<String, Object> getStudent() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        MyUserDetails myUserDetails = JSONObject.parseObject(principal.toString(), MyUserDetails.class);
        Student student = myUserDetails.getStudent();

        HashMap<String, Object> result = new HashMap<>();
        result.put("student", student);
        result.put("message", "需要权限的资源访问成功");
        return result;
    }
  1. 看到 @PreAuthorize(" @ms.check(‘bandTestQuery’) ") 这里是我自定义 检测方法 可以使用框架自带的 hasAuthority 也是完全可以的

总结一下思路

在这里插入图片描述

下面说一下多个参数的验证

N个字段来确定一个用户,但是密码只有一个字段

之前我们都是基于 账号+密码 进行验证的,实际上大部分时间也都是如此,但是有的时候业务需求原因,会有多个参数进行验证
但是对于我的理解而言,不论是几个参数的验证,都可以转换成 账号+密码的模式

username并不是数据库的某个字段,而是理解成 能够唯一确定某个用户所需要的参数
比如,现在有三个参数:

学生手机号 	studentMobile
教师手机号 	teacherMobile
密码				password

在系统中,学生的手机号+教师手机号 可以唯一确定一条数据,密码依旧是密码
那么 username其实就可以拼接成 studentMobile:teacherMobile
这样其实还是可以使用 usernamePasswordAuthenticationToken 的,只不过我们在查询数据库的时候,执行下面的代码:

        String[] split = username.split(":");
        String studentMobile = split[0];
        String teacherMobile = split[1];

这样就可以将这两条数据拆出来,然后根据数据库怎么查,这个就根据自己公司的业务了

所以,这种解决方案适合: N个字段来确定一个用户,但是密码只有一个字段

N个字段来确定一个用户,密码也有N个字段

我们先来看一下之前的流程图
在这里插入图片描述
第7步 可以看到 密码的对比是在 DaoAuthenticationProvider 来对比的,我们找到这段代码来看看
在这里插入图片描述
可以看到,只对比了密码,所以说,如果是通过N个字段来判断密码是否正确,根据之前的思路需要怎么弄呢?

例如:现在密码需要两个字段来判断

密码: 	password
验证码:	code

那么还是在生成 authentication 的时候,传入的值是 : password+code
而且还需要修改这里
在这里插入图片描述
看起来很麻烦。这种方式我自己也没有尝试过,但是理论上是可以的

更为通用的方法,重写 xxxProvider

还是看一下流程图
在这里插入图片描述
authenticationManager.authenticate方法是入口,provider来对比密码,service是查看数据库

同样的,首先是写一个 三个参数的 入参,查数据库的service,也重新写一个
入参如下:

public class LoginWith3ParamToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 560L;
    private final Object principal;
    private Object credentials;
    private Object teacherUuid;//加一个参数
    
    public LoginWith3ParamToken(Object principal, Object credentials, Object teacherUuid) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.teacherUuid = teacherUuid;//加一个参数
        this.setAuthenticated(false);
    }

    public LoginWith3ParamToken(Object principal, Object credentials, Object teacherUuid, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        this.teacherUuid = teacherUuid;//加一个参数
        super.setAuthenticated(true);
    }

    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public Object getTeacherUuid() {//加一个参数
        return this.teacherUuid;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}

service如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginWith3ParamUserDetails implements UserDetails {

    private Student student;

    private Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return student.getPassword();
    }

    @Override
    public String getUsername() {
        return student.getStudentName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

调用入口的时候,也用三参的,

@PostMapping("/loginWith3Param")
    public String loginWith3Param(@RequestBody LoginWith3ParaModel model) {
        Authentication loginWith3ParamToken = new LoginWith3ParamToken(model.getUsername(), model.getPassword(),
                model.getTeacherUuid());//使用三个参数的
        Authentication authenticate = authenticationManager.authenticate(loginWith3ParamToken);
        return JSON.toJSONString(authenticate);
    }

那么既然要对比密码,也就是重写 provider,那怎么重写?我不会,但是框架会,我们找到 usernamePassword的provider
发现
在这里插入图片描述
继承了 AbstractUserDetailsAuthenticationProvider,而
在这里插入图片描述
AbstractUserDetailsAuthenticationProvider 实现了 AuthenticationProvider
我们看一下 AuthenticationProvider的实现类

这两个就是我们刚才说的两个类
那么我们就把这两个类的代码复制一份,

  1. 把AbstractUserDetailsAuthenticationProvider里面的 UsernamePasswordAuthenticationToken 全部改成自己的 LoginWith3ParamToken
  2. 把 DaoAuthenticationProvider 里面的 UsernamePasswordAuthenticationToken 也都改成LoginWith3ParamToken,然后找到additionalAuthenticationChecks这个方法,我们来看一眼这个方法
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();  /*获取密码*/
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {  /*对比密码*/
                this.logger.debug("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }

其中重要的代码就是 对比密码,所以说,我们需要改动的点,就找到了,就在这个方法里面
我是这样改的

LoginWith3ParamUserDetails loginWith3ParamUserDetails = (LoginWith3ParamUserDetails)userDetails;
        if (authentication.getCredentials() == null) {
            throw new RuntimeException("密码获取不到");
        } else {
            String presentedPassword = authentication.getCredentials().toString();   
            String presentedTeacherUuid=authentication.getTeacherUuid().toString();   
            if (
                    !this.passwordEncoder.matches(presentedPassword, loginWith3ParamUserDetails.getPassword())
                            || !presentedTeacherUuid.equals(loginWith3ParamUserDetails.getStudent().getTeacheruuid())
            ) {   
                throw new RuntimeException("密码和教师uuid对比失败");
            }
        }

ok,到这里其实所有的准备工作都做完了,但是各位有没有想过,东西我们写出来了。怎么给框架管理呢?我们写的东西总要和框架产生一些关联吧。我们是创建了很多新的类,而不是重写了框架中原有的方法,所以最后一步,将我们写的东西托给框架管理


@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyJwtTokenFilter myJwtTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean   /*todo-1 将 AuthenticationManager注入容器*/
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override   /*todo-8 这里需要配置一下放行的接口*/
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/security/hello").permitAll() /*公共的资源,比如,首页,广告等,所有人都可以访问*/
                .antMatchers("/security/loginWith2Param").anonymous()/*登陆页面只允许 匿名访问,就是没登陆的人访问*/
                .antMatchers("/security/loginWith3Param").anonymous()/*登陆页面只允许 匿名访问,就是没登陆的人访问*/
                .anyRequest().authenticated();

        http.addFilterBefore(myJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);

        http.cors();/*允许跨域*/
    }

    @Autowired
    private LoginWith3ParamServiceImpl loginWith3ParamService;

    @Autowired
    private MyUserDetailsServiceImpl myUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        super.configure(auth);
        LoginWith3ParamDaoAuthenticationProvider loginWith3ParamDaoAuthenticationProvider = new LoginWith3ParamDaoAuthenticationProvider();
        loginWith3ParamDaoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        loginWith3ParamDaoAuthenticationProvider.setUserDetailsService(loginWith3ParamService);
        auth.authenticationProvider(loginWith3ParamDaoAuthenticationProvider);

        /*这个是 框架自带的UsernamePassword里面的 Provider  ,因为我们要将自己的 provider添加进去,所以重写了
        * configure(AuthenticationManagerBuilder auth)这个方法,虽然可以加在我们自己的provider了,但是
        * 带来的结果就是,原本框架自带的一些provider不再自动加载、需要我们自己手动加载*/
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
        auth.authenticationProvider(daoAuthenticationProvider);




    }
}