• 【Java】Spring scurity + JWT 前后端分离


    依赖,及其他配置请查看上一篇
    Spring Security 简单上手

    说明:

    在默认情况下 Spring Security 是按照以下流程进行的:
    UsernamePasswordAuthenticationFilter
    Authentication
    AuthenticationManager
    AuthenticationProvider
    UserDetailsService
    // 回到起点进行后续操作,比如缓存认证信息到session和调用成功后的处理器等等
    UsernamePasswordAuthenticationFilter

    在这里插入图片描述

    (一)父类的处理流程

    首先我们先了解一下父类【AbstractAuthenticationProcessingFilter】的处理流程。
    其中 doFilter() 方法 就是入口。

    来到 AbstractAuthenticationProcessingFilter.doFilter()
    如下 if 逻辑中首先判断当前的filter是否可以处理当前请求,不可以的话则交给下一个filter处理。

    在这里插入图片描述

    通过判断后就会 调用此抽象类的子类:
    UsernamePasswordAuthenticationFilter.attemptAuthentication(request, response)
    方法做具体的操作。

    具体实现过程如下:
    在这里插入图片描述

    点进去我们可以看到
    调用了子类(UsernamePasswordAuthenticationFilter)的方法很关键!!!
    在这里插入图片描述
    在这里插入图片描述
    从这里我们不妨去看看这个UsernamePasswordAuthenticationFilter过滤器
    在这里插入图片描述

    从代码中可以看出,这个过滤器只会处理POST请求
    在这里插入图片描述
    并且验证后会产生一个UsernamePasswordAuthenticationToken
    在这里插入图片描述
    后面我们自己需要自定义过滤器,也是按照这个思路。

    回到父类 AbstractAuthenticationProcessingFilterdoFilter 方法中:

    可以看到 最终认证成功后做一些成功后的session操作,比如将认证信息存到session等

    在这里插入图片描述

    最终是认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中并调用成功处理器做相应的操作。

    在这里插入图片描述
    在这里插入图片描述
    以上就是父类的大致的处理流程。

    (二) 编写配置类

    编写 OaApiSecurityConfig

    首先编写Spring Security 的配置类【OaApiSecurityConfig】:

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用权限验证
    public class OaApiSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().configurationSource(configurationSource());   //跨域配置
            http.antMatcher("/api/**")  //指定以/api/开头的请求使用以下配置
                    .authorizeHttpRequests()
                    .antMatchers(HttpMethod.OPTIONS).permitAll()  //请求方法OPTIONS放行
                    .antMatchers("/api/login", "/api/", "/api/chart/**").permitAll() // 直接放行的uri
                    .anyRequest().authenticated()
                    .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().csrf().disable();
        }
        /**
         * 跨域问题
         * @return
         */
            private CorsConfigurationSource configurationSource() {
            UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
            corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
            corsConfiguration.setAllowedMethods(Arrays.asList("*"));
            corsConfiguration.setExposedHeaders(Arrays.asList("*"));
            urlBasedCorsConfigurationSource.registerCorsConfiguration("/api/**", corsConfiguration);
            return urlBasedCorsConfigurationSource;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 这个类继承了WebSecurityConfigurerAdapter,以及需要使用@Configuration注解使此配置类生效;
    • 这个类重写了configure方法,参数为Security 提供的HttpSecurity ,以上代码,首先是对跨域问题进行解决;

    编写 ApiAuthticationFilter

    编写【ApiAuthticationFilter】过滤器 , 开始对UsernamePassword进行认证;
    在这里插入图片描述
    如上图:可以看出,在默认UsernamePasswordAuthenticationFilter方法中是继承了AbstractAuthenticationProcessingFilter ,所以我们自己要想实现这个流程,自己编写的filter也需要继承该类,代码如下:

    @Slf4j
    public class ApiAuthticationFilter extends AbstractAuthenticationProcessingFilter {
        public ApiAuthticationFilter(String defaultFilterProcessesUrl) {
            super(defaultFilterProcessesUrl);
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            log.debug("进入ApiAuthticationFilter");
            ObjectMapper objectMapper = new ObjectMapper();
            Map<String, String> map = objectMapper.readValue(request.getInputStream(), Map.class);
            String account = map.get("account");
            String password = map.get("password");
            log.debug("Account:{},Password:{}", account, password);
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(account,
                    password);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里对为什么需要写自定义的过滤器,而不用Spring Security 提供的UsernamePasswordAuthenticationFilter,
    这是因为现在是前后端分离开发,前端传过来的是json格式数据,而原本的是处理不了这种格式数据的,就需要我们自己处理后在进行认证。

    这里对上面的ApiAuthticationFilter 代码作一下解释:

    由于前端传来的是json格式数据,如下是将用户账号密码取出:

     	ObjectMapper objectMapper = new ObjectMapper();
        Map<String, String> map = objectMapper.readValue(request.getInputStream(), Map.class);
        String account = map.get("account");
        String password = map.get("password");
    
    • 1
    • 2
    • 3
    • 4

    调用UsernamePasswordAuthenticationToken 进行认证处理

    从上面对父类的分析得出:
    默认的UsernamePasswordAuthenticationFilter 中会调用此方法来产生token

    UsernamePasswordAuthenticationToken authRequest = 
    UsernamePasswordAuthenticationToken.unauthenticated(account,password);
    
    • 1
    • 2

    子类流程分析

    UsernamePasswordAuthenticationToken 流程

    里面的流程如下:

    调用unauthenticated方法后:里面会new一个UsernamePasswordAuthenticationToken

    在这里插入图片描述
    在这里插入图片描述

    那么在这里为什么这个构造器设置权限为null?

    super((Collection)null);

    并且设置是否授权为false?

    this.setAuthenticated(false);

    因为我们这是刚刚登陆过来,账号密码都没验证,所以这里是未授权,权限null。

    现在回到ApiAuthticationFilter中的:

    return this.getAuthenticationManager().authenticate(authRequest);

    这句代码意思就是:调用AuthenticationManagerauthenticate方法进行验证。

    AuthenticationManager处理流程

    由如下这句代码触发

    return this.getAuthenticationManager().authenticate(authRequest);

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    通过上面对源码的一步步分析查找
    发现他交由AuthenticationManager接口的ProviderManager实现类处理。

    ProviderManager

    如下,最终来到了此方法:
    在这里插入图片描述

    如下代码会遍历所有的Providers,然后依次执行验证方法看是否支持

    在这里插入图片描述

    若有一个能够支持当前token,则直接交由此provider处理并break

    在这里插入图片描述

    若没一个provider验证成功,则交由父类来尝试处理

    在这里插入图片描述
    在这里插入图片描述

    AuthenticationProvider处理流程

    上面已经来到了provider 的处理步骤,provider会调用authenticate 来进行处理。
    通过如下代码触发:

    result = provider.authenticate(authentication);

    这里交由AuthenticationProvider 接口的实现类 DaoAuthenticationProvider来处理。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    我们来到DaoAuthenticationProvider
    发现继承了AbstractUserDetailsAuthenticationProvider
    在这里插入图片描述
    来到:AbstractUserDetailsAuthenticationProvider

    发现 AbstractUserDetailsAuthenticationProvider.authenticate() 首先调用了user
    = this.retrieveUser(username(UsernamePasswordAuthenticationToken)authentication);

    在这里插入图片描述
    所以在DaoAuthenticationProvider
    调用的是DaoAuthenticationProvider.retrieveUser()
    在这里插入图片描述
    在这里插入图片描述

    可以看到这里 调用UserDetailsService接口的loadUserByUsername方法

    • 此方法就是我们自己定义的类去实现接口重写的方法,处理我们自己的业务逻辑。

    上面对整个流程大致的分析完了,后面就是实现自己的逻辑

    (三)完善配置类

    上面编写的OaApiSecurityConfig已经对跨域进行了处理,并且也已经编写 ApiAuthticationFilter过滤器
    现在需要将该过滤器添加进去

    //将ApiAuthticationFilter配置到UsernamePasswordAuthenticationFilter 前面
            http.addFilterBefore(apiAuthticationFilter(), UsernamePasswordAuthenticationFilter.class);
    
    • 1
    • 2

    在这里插入图片描述

    在结合上面的分析
    还需要编写provider来处理token

    在配置类添加

     	@Autowired
        private UserDetailsService userDetailsService;
    	@Bean
        public DaoAuthenticationProvider daoAuthenticationProvider() {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
            daoAuthenticationProvider.setUserDetailsService(userDetailsService);
            daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
            return daoAuthenticationProvider;
        }
        
        /**
         * 密码加密
         *
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    通过上面对子类流程的分析最后调用的是DaoAuthenticationProvider ,这里需要实现自己逻辑就要 new DaoAuthenticationProvider();
    而这个方法中会调用UserDetailsService接口的loadUserByUsername方法

    在这里插入图片描述

    实现自己的逻辑就要实现该接口,重写此方法

    编写OaUserDetailsService

    @Slf4j
    @Component
    public class OaUserDetailsService implements UserDetailsService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //根据账号去查询用户信息,把用户的权限查出来
            log.debug("用户账号:{}", username);
            com.manager.oa.pojo.rbac.User user = new com.manager.oa.pojo.rbac.User();
            user.setAccount(username);
            List<com.manager.oa.pojo.rbac.User> users = userMapper.findUsersByCondition(user);
            if (users.size() == 0) {//避免空指针
                throw new UsernameNotFoundException("账号不存在");    //抛出异常
            }
            log.debug("数据库信息:{}", users.get(0));
            log.debug("用户权限信息:{}", users.get(0).getRole().getPermissions());
            List<SimpleGrantedAuthority> list = new ArrayList<>();
            for (Permission p : users.get(0).getRole().getPermissions()
            ) {
                //过滤父权限
                if (p.getPerPower() == null) {
                    continue;
                }
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(p.getIdentify());
                list.add(simpleGrantedAuthority);
            }
            return new OaUser(username, users.get(0).getPassword(), users.get(0), list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    上面代码是我自己需要实现的逻辑;

    可以看到该方法返回的是Spring Security 的User类,下面是User类中的属性,发现里面的属性太单一,如果要加自己的属性的话就要继承这个类
    在这里插入图片描述
    以下是我编写的OaUser继承User
    在这里插入图片描述
    最后OaUserDetailsService就可以返回自己的OaUser

    这里我对OaUser几个参数作说明:
    在这里插入图片描述
    第一个参数:username:前端传来的需要验证的账号
    第二个参数:数据库中的密码
    第三个参数:需要的数据
    第四个参数:权限的集合

    最后在配置了添加

    // 实例化过滤器中
        public ApiAuthticationFilter apiAuthticationFilter() throws Exception {
            // 构造方法的参数是一个URL,只有该URL的请求才回进入该过滤器
            ApiAuthticationFilter apiAuthticationFilter = new ApiAuthticationFilter("/api/login");
            apiAuthticationFilter.setAuthenticationManager(authenticationManager());
            // 认证成功的回调
            apiAuthticationFilter.setAuthenticationSuccessHandler((req, resp, authtication) -> {
                OaUser user = (OaUser) authtication.getPrincipal();
                Map<String, Object> map = new HashMap<>();
                map.put("account", user.getUser().getAccount());
                map.put("id", user.getUser().getId());
                String token = jwtUtil.createJWT(map);
                String r = new ObjectMapper().writeValueAsString(new ResponseEntity<>(token));
                log.debug("【系统日志】Token->{}", token);
                resp.setContentType("application/json;charset=UTF-8");
                resp.getWriter().write(r);
                resp.getWriter().close();
            });
            // 认证失败
            apiAuthticationFilter.setAuthenticationFailureHandler(((request, response, exception) -> {
                String r = new ObjectMapper().writeValueAsString(ResponseEntity.FAIL);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(r);
                response.getWriter().close();
            }));
    
            return apiAuthticationFilter;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    最后在需要控制权限的controller上添加如下注解实现权限控制

    @PreAuthorize(“hasAuthority(‘dept:list’)”) // 需要的权限

    @PreAuthorize("hasAuthority('dept:list')") // 需要的权限
        @GetMapping("/list")
        public ResponseEntity<List<Dept>> getAll() {
            return new ResponseEntity<>(deptService.list());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在配置类上添加

    @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用权限验证
    在这里插入图片描述

    这是前端vue代码
    用到了element-ui,具体配置在官网查看 element-ui

    <template>
      <div>
        <div class="bg">div>
        <div class="container">
          <div class="line bouncein">
            <div class="xs6 xm4 xs3-move xm4-move" id="app">
              <div style="height: 150px">div>
              <div class="media media-y margin-big-bottom">div>
              <input type="hidden" name="opr" value="login" />
              <div class="panel loginbox">
                <div class="text-center margin-big padding-big-top">
                  <h1>管理中心h1>
                div>
                <div
                  class="panel-body"
                  style="padding: 30px; padding-bottom: 10px; padding-top: 10px"
                >
                  <div class="form-group">
                    <div class="field field-icon-right">
                      <input
                        type="text"
                        class="input input-big"
                        placeholder="登录账号"
                        value=""
                        v-model="user.account"
                      />
                      <span class="icon icon-user margin-small">span>
                    div>
                  div>
                  <div class="form-group">
                    <div class="field field-icon-right">
                      <input
                        type="password"
                        class="input input-big"
                        placeholder="登录密码"
                        value=""
                        v-model="user.password"
                      />
                      <span class="icon icon-key margin-small">span>
                    div>
                  div>
                  <div style="padding: 30px">
                    <input
                      type="button"
                      class="button button-block bg-main text-big input-big"
                      value="登录"
                      v-on:click="login"
                    />
                  div>
                div>
              div>
            div>
          div>
        div>
      div>
    template>
    <script >
    export default {
      data() {
        return {
          msg: "账号密码错误",
          user: {
            account: "admin3",
            password: "123123",
          },
        };
      },
      methods: {
        login: function () {
          let that = this;
          this.axios
            .post("http://localhost:8080/api/login", this.user)
            .then(function (r) {
              if (r.data.code == "200") {
                sessionStorage.setItem("token", r.data.data);
                location.href = "index.html";
              } else if (r.data.code == "100") {
                that.$message({
                  message: r.data.msg,
                  type: "warning",
                });
              } else {
                that.msg = r.data.msg;
                that.$message.error(r.data.msg);
              }
            });
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    以上就实现了通过spring Security 对账号密码权限控制

    (四)Spring Security + jwt

    通过以上,已经对整个流程有了一个大致的了解,现在来集成jwt

    准备工作:

    1、引入依赖

    
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.7.13version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、添加配置

    jwt:
      secretKey: KxK7f3yaSfOXVwvuYJDozvQ7Mt1JnqRX
      expireSeconds: 60
    
    • 1
    • 2
    • 3

    3、编写工具类

    这是对jwt验证,生成类

    @Component
    @Slf4j
    public class JwtUtil {
    
        // 秘钥
        @Value("${jwt.secretKey}")
        private String secretKey;
        // 过期时间
        @Value("${jwt.expireSeconds}")
        private String expireSeconds;
    
    
        {
            log.debug("【jwtUtil】secretKey->{}", secretKey);
        }
    
        /**
         * 产生Token
         *
         * @param map 需要载荷的数据 map类
         * @return
         */
        public String createJWT(Map<String, Object> map) {
            //当前时间
            DateTime now = DateTime.now();
            // 设置过期时间
            DateTime newTime = now.offsetNew(DateField.MINUTE, Integer.parseInt(expireSeconds));
    
            Map<String, Object> payload = new HashMap<String, Object>();
            //签发时间
            payload.put(JWTPayload.ISSUED_AT, now);
            //过期时间
            payload.put(JWTPayload.EXPIRES_AT, newTime);
            //生效时间
            payload.put(JWTPayload.NOT_BEFORE, now);
            //载荷
            Set<String> keySet = map.keySet();
            for (String key : keySet
            ) {
                payload.put(key, map.get(key));
            }
            // 产生Token
            return JWTUtil.createToken(payload, secretKey.getBytes());
        }
    
        /**
         * 验证内容
         *
         * @param token
         * @return
         */
        public boolean verifyJWT(String token) {
            JWT jwt = JWTUtil.parseToken(token);
            boolean verifyKey = jwt.setKey(secretKey.getBytes()).verify();
            return verifyKey;
        }
    
        /**
         * 验证是否过期
         *
         * @param token
         * @return
         */
        public boolean verifyTimeJWT(String token) {
            try {
                JWTValidator.of(token).validateDate(DateUtil.date());
                return true;
            } catch (ValidateException e) {
                e.printStackTrace();
                return false;
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    集成

    按照思路我们首先需要编写一个filter 来对jwt进行校验
    然后需要准备一个Provider,重新authenticate 实现自己的验证逻辑 ,添加到这个流程中去

    1、编写自己的Token

    注意 按照上面分析框架默认的,他自己会产生一个token,
    但是我们这里不能用他的token,我们需要自己按照自己的规则来产生一个token
    所以我们需要继承AbstractAuthenticationToken来实现自己的token

    public class JwtToken extends AbstractAuthenticationToken {
        private String jwt;
        public JwtToken(String jwt) {
            super(null);
            this.jwt = jwt;
            setAuthenticated(false);
        }
    
        /**
         * Creates a token with the supplied array of authorities.
         *
         * @param authorities the collection of GrantedAuthoritys for the principal
         *                    represented by this authentication object.
         */
        public JwtToken(String jwt,boolean isAuthenticated, Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.jwt = jwt;
            setAuthenticated(isAuthenticated);
        }
    
        @Override
        public Object getCredentials() {
            return jwt;
        }
    
        @Override
        public Object getPrincipal() {
            return jwt;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2、编写Provider

    @Slf4j
    public class JwtVerifyProvider implements AuthenticationProvider {
        @Autowired
        private JwtUtil jwtUtil;
        @Autowired
        private UserService userService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            JwtToken jwtToken = (JwtToken) authentication;
            String jwt = (String) jwtToken.getPrincipal();
            if (jwt == null) {
                throw new ValidateException("令牌无效");
            }
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            if (jwtUtil.verifyJWT(jwt)) {
                JWT jwtFlag = JWTUtil.parseToken(jwt);
                User DBUser = userService.getById((Integer) jwtFlag.getPayload("id"));
                log.debug("【jwtVerify:系统日志】:{} -> 正在进行jwt验证...-》", DBUser.getAccount());
                log.debug("【jwtVerify:系统日志】: 权限->{}", DBUser.getRole().getPermissions());
                List<Permission> perms = DBUser.getRole().getPermissions();
                for (Permission perm : perms) {
                    authorities.add(new SimpleGrantedAuthority(perm.getIdentify()));
                }
                // 产生已认证的JwtToken
                JwtToken authenticatedToken = new JwtToken(jwt, true, authorities);
                // 保存起来
                SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
                return authenticatedToken;
            } else if (!jwtUtil.verifyTimeJWT(jwt)) {
                throw new ValidateException("令牌过期");
            } else {
                throw new ValidateException("令牌无效");
            }
        }
    
        /**
         * 将JwtToken新增到authentication
         * 这里需要将我们自己的JwtToken添加到authentication,后面ProviderManager会循环比对
         * @param authentication
         * @return
         */
        @Override
        public boolean supports(Class<?> authentication) {
            return JwtToken.class.isAssignableFrom(authentication);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    3、添加Provider

    在配置类【OaApiSecurityConfig】中添加

       /**
         * 将两个Provier增加到Security中 ,默认只有daoAuthenticationProvider 一个
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //将两个Provier增加到Security中
            auth.authenticationProvider(daoAuthenticationProvider()).authenticationProvider(jwtVerifyProvider());
        }
         /**
         * JwtVerifyProvider
         *
         * @return
         */
        @Bean
        public JwtVerifyProvider jwtVerifyProvider() {
            return new JwtVerifyProvider();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4、编写Filter

    @Slf4j
    public class JwtVerifyFilter extends AbstractAuthenticationProcessingFilter {
    
        public JwtVerifyFilter(String defaultFilterProcessesUrl) {
            super(defaultFilterProcessesUrl);
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            log.debug("进入到jwt过滤器");
            String jwt = request.getHeader("X-Token");
            log.debug("jwt{}",jwt);
            JwtToken jwtToken = new JwtToken(jwt);
            return this.getAuthenticationManager().authenticate(jwtToken);
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            attemptAuthentication((HttpServletRequest)request,(HttpServletResponse)response);
            chain.doFilter(request, response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5、将Filter添加到过滤器链中

    在配置类【OaApiSecurityConfig】中添加

    // 实例化过滤器中
    public JwtVerifyFilter jwtVerifyFilter() throws Exception {
        //构造方法的参数是一个URL,只有该URL的请求才回进入该过滤器
        JwtVerifyFilter jwtVerifyFilter = new JwtVerifyFilter("/api/**");
        jwtVerifyFilter.setAuthenticationManager(authenticationManager());
        //认证成功的回调
        jwtVerifyFilter.setAuthenticationSuccessHandler((req, resp, authtication) -> {
            String r = new ObjectMapper().writeValueAsString(ResponseEntity.SUCCESS);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(r);
            resp.getWriter().close();
        });
    
        return jwtVerifyFilter;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在配置类【OaApiSecurityConfig】中的configure方法添加

     http.addFilterAfter(jwtVerifyFilter(), ApiAuthticationFilter.class);
    
    • 1

    在这里插入图片描述

    测试

    1、前端Vue

    <template>
      <div>
        <div class="bg">div>
        <div class="container">
          <div class="line bouncein">
            <div class="xs6 xm4 xs3-move xm4-move" id="app">
              <div style="height: 150px">div>
              <div class="media media-y margin-big-bottom">div>
              <input type="hidden" name="opr" value="login" />
              <div class="panel loginbox">
                <div class="text-center margin-big padding-big-top">
                  <h1>蜗牛学苑后台管理中心h1>
                div>
                <div
                  class="panel-body"
                  style="padding: 30px; padding-bottom: 10px; padding-top: 10px"
                >
                  <div class="form-group">
                    <div class="field field-icon-right">
                      <input
                        type="text"
                        class="input input-big"
                        placeholder="登录账号"
                        value=""
                        v-model="user.account"
                      />
                      <span class="icon icon-user margin-small">span>
                    div>
                  div>
                  <div class="form-group">
                    <div class="field field-icon-right">
                      <input
                        type="password"
                        class="input input-big"
                        placeholder="登录密码"
                        value=""
                        v-model="user.password"
                      />
                      <span class="icon icon-key margin-small">span>
                    div>
                  div>
                  <div style="padding: 30px">
                    <input
                      type="button"
                      class="button button-block bg-main text-big input-big"
                      value="登录"
                      v-on:click="login"
                    />
                  div>
                div>
              div>
            div>
          div>
        div>
      div>
    template>
    <script >
    export default {
      data() {
        return {
          msg: "账号密码错误",
          user: {
            account: "admin3",
            password: "123123",
          },
        };
      },
      methods: {
        login: function () {
          let that = this;
          this.axios
            .post("http://localhost:8080/api/login", this.user)
            .then(function (r) {
              if (r.data.code == "200") {
                sessionStorage.setItem("token", r.data.data);
                location.href = "index.html";
              } else if (r.data.code == "100") {
                that.$message({
                  message: r.data.msg,
                  type: "warning",
                });
              } else {
                that.msg = r.data.msg;
                that.$message.error(r.data.msg);
              }
            });
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    (五)总结

    主要是流程

    UsernamePasswordAuthenticationFilter
    Authentication
    AuthenticationManager
    AuthenticationProvider
    UserDetailsService //回到起点进行后续操作,比如缓存认证信息到session和调用成功后的处理器等等
    UsernamePasswordAuthenticationFilter

    按照这个流程,对比默认的方式来实现自己的逻辑

  • 相关阅读:
    3.PHP数据类型、常量、字符串和运算符
    软考高级之系统架构师之数据流图和流程图
    MCE | 肝炎病毒是如何诱发肝癌的
    2023下半年创业风口项目:实景自动无人直播!揭秘3大好处!
    Epuck2机器人固件更新及IP查询
    云服务器和物理服务器的区别在哪
    perspectiveTransform warpPerspective getPerspectiveTransform findHomography
    【收藏】使用jieba 进行基于Paddle的词性标注
    Linux中udp服务端,客户端的开发
    PHP 发送邮件
  • 原文地址:https://blog.csdn.net/weixin_44798538/article/details/126470083