• spring boot + spring security +JWT令牌 +前后端分离--- 心得


    1.前言

    观看这篇随笔需要有spring security基础。

    心得:
    
    1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定
    2.生成的token是将登录通过的用户的权限拼接的字符串加密后放入里面后加密,当携带token访问时被拦截后,会将token解析出的权限注册,因为不与数据库等数据共享校验权限最新信息,
    如果在携带token的请求前权限有变化,但是token却没有改变,会导致token权限与用户真实权限不一致,形成脏数据啦!!!
    如果权限增加还好,使得无法访问新加权限的操作,如果是减少权限,比如vip过期,用户仍然可以有vip权限。
    3.解决token脏数据的方案有两个:
    (1)等待该token失效时间【不靠谱】;
    (2)每次修改权限时,会强制使得token失效,具体怎么做,还没试过
    4.当然,也有优点的,不与数据库等最新数据做权限对比操作,较少了访问数据库该用户信息的部分,能快速的过滤请求权限,理论上访问数据会变快。
    5.可以设置过期时间,单位毫秒,用时间戳设置 ,到时间则不可在使用,
    但是缺点很明显,在未过期之前,可以无数次访问验证通过,无法控制使用次数,
    因此不能作为资源服务器对第三方应用开放的授权令牌,
    6.令牌格式对不上,会直接报错异常,为了服务降级,做个异常捕获即可
    7.如果生成了新的令牌,旧的令牌仍然可以使用,因此会导致多设备同时登录的情况,无法控制登录数量
    8.使用jwt[java web token],做登录校验,则会导致http.sessionManagement().maximumSessions(1);设置失效,因为没有使用session做为登录控制
    //
    安全弊端很多 , 但是让我深刻明白了token的内部思想
    完全可以使用redis来完成用户token的管理,但是这样每次都需要向redis查询一次,就让jave web token 不伦不类了 。。。。
    虽然已经尽可能的让服务器减少负担和提高反应速度,但仍然感觉好鸡肋
    //
    当然应用场景还是有的,可用于分享连接的使用,这样既能向有令牌的用户使用被分享的权限,而且不需要去数据库获取数据对其进行对比,降低服务器负担,
    也就是说比较适合半开放性的功能使用
    //
    工程我放在GitHub仓库了
    https://github.com/cen-xi/spring-security-JWT

    2.操作

    看我的源码大招,写了很多注释了,足够详细了,我懒得再写说明

     (1)目录结构

    (2)添加依赖包

     pom.xml源码

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.0.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>security-jwt-5605</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>security-jwt-5605</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!-- jwt依赖-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
    
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    (3)做一个实体类,继承  UserDetails

    package com.example.securityjwt5605.model;
    
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    
    public class JwtUser implements UserDetails {
        //属性名  username 和  password 是固定死的,不可更改,否则报错
        //但 grantedAuthorities 可随意
    
        private String username;
        private String password;
        private List<GrantedAuthority> grantedAuthorities;
    
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return grantedAuthorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        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;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public void setGrantedAuthorities(List<GrantedAuthority> grantedAuthorities) {
            this.grantedAuthorities = grantedAuthorities;
        }
    }
    View Code

    (4)用户名密码登录过滤器

    package com.example.securityjwt5605.filters;
    
    import com.example.securityjwt5605.model.JwtUser;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.http.MediaType;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.security.web.util.matcher.RequestMatcher;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Collection;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 首次登录才调用这个方法
     */
    
    public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
        //构造方法 ,记得使用 public
        //第一个是 登录路径 。第二个是 认证管理者
        //在启动的时候就已经h已经执行了
        public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
            super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
            System.out.println("===============================登录拦截1==================================");
            //存储到父类,可不加 super.便于方法 attemptAuthentication()调用,
            setAuthenticationManager(authenticationManager);
    
        }
    
    
        /**
         *访问/login登录后首先进入这里
         */
        @Override
        public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {
            JwtUser user = new JwtUser();
            System.out.println("===============================登录拦截2==================================");
            try {
                //从请求中获取用户验证信息
                //将json字符串解析
                 user = new ObjectMapper().readValue(req.getInputStream(), JwtUser.class);
    //        }catch (Exception ignored){
    //            //Exception ignored表示忽略异常
    //            //这样内部可以不写内容
    //        }
    //            String username = req.getParameter("username");
    //            String password = req.getParameter("password");
    //            if (username == null || password == null){
    //                throw new Exception();
    //            }
    //            user.setUsername(username);
    //            user.setPassword(password);
            }catch (Exception e){
                //Exception ignored表示忽略异常
                System.out.println("请求无法解析出JwtUser对象");
    
            }
            //对请求做认证操作,如何校验,由默认的程序完成,不涉及对比操作,因为用户信息存在内存中,否则需要修改 securityConfig.java 的 configure(AuthenticationManagerBuilder auth) 用于设置数据库操作
            //认证管理对象执行认证方法,new 一个用户密码认证令牌对象,参数为用户名和密码,然后放入认证方法中
            //然后执行登录验证
            return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
    
    
        }
    
    
        //认证成功
        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    
            System.out.println("===============================登录拦截3==================================");
            //获取登录角色的权限
            //这是权限 ,如果登录内存只有角色配置,无权限配置,则自动添加前缀构成权限 ROLE_角色
            Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
            //线程安全
            StringBuffer stringBuffer = new StringBuffer();
            for (GrantedAuthority grantedAuthority : authorities) {
                System.out.println("当前有的权限:"+grantedAuthority);
                //用逗号隔开好一点,不然后面需要手动切割
                stringBuffer.append(grantedAuthority.getAuthority()).append(",");
            }
            //生成令牌 token
            String jwt = Jwts.builder()
                    //登录角色的权限,这会导致如果权限更改,该token无法及时更新权限信息
                    .claim("authorities", stringBuffer)
                    //用户名
                    .setSubject(authResult.getName())
                    //存活时间,过期则判为无效
                    .setExpiration(new Date(System.currentTimeMillis() + 1000 * 10))
                    //签名,第一个参数时算法,第二个参数时内容,内容可随意写
                    .signWith(SignatureAlgorithm.HS512, "java521@java")
                    //协议完成
                    .compact();
            System.out.println(jwt);
            System.out.println("======================");
            System.out.println(stringBuffer);
            //设置json数据返回给前端
            Map<String, Object> map = new HashMap<>();
            map.put("token", jwt);
            map.put("msg", "登录成功");
            //MediaType.APPLICATION_JSON_UTF8_VALUE 等用于  "application/json;charset=UTF-8"
    //        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.setContentType("application/json;charset=utf-8");
            PrintWriter printWriter = response.getWriter();
            //转成json后传送
            printWriter.write(new ObjectMapper().writeValueAsString(map));
            //关闭流
            printWriter.flush();
            printWriter.close();
    
        }
    
        //认证失败
        @Override
        protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
            System.out.println("===============================登录拦截4==================================");
            Map<String, Object> map = new HashMap<>();
            map.put("msg", "登录失败");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter printWriter = response.getWriter();
            //转成json后传送
            printWriter.write(new ObjectMapper().writeValueAsString(map));
            //关闭流
            printWriter.flush();
            printWriter.close();
        }
    }
    View Code

     (5)token访问过滤器

    package com.example.securityjwt5605.filters;
    
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.web.filter.GenericFilterBean;
    import sun.plugin.liveconnect.SecurityContextHelper;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.security.Security;
    import java.util.List;
    
    /**
     * 对携带token的请求做token检查,对比是否正确,正确则可以直接通过
     */
    
    public class JwtFilter extends GenericFilterBean {
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("===============================token登录拦截1==================================");
    
            //强转http请求
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            //从请求头获取数据
            //定死了名称为 authorization
            String tokenStr = httpServletRequest.getHeader("authorization");
            System.out.println(tokenStr);
            /*
            打印结果 【不可换行,这里为了展示才换行】
            Bearer eyJhbGciOiJIUzUxMiJ9.eyJhdXRob3JpdGllcyI6IlJPTEVfYWRtaW4sIiwic3ViIjoiYWRtaW4iLCJleHAi
            OjE1OTE2MzAwMTF9.oHmTl-f5RetmFJ8rM5MaIruOkA83sqt-6F7f2c27QRWdJvTAOIYX_VbRCngodaROZ4jprQ1ktwz5sZAWcDJdkg
    
             */
    
            System.out.println("==========================================");
            if (tokenStr != null) {
                System.out.println("有认证令牌");
                boolean k = true;
                Jws<Claims> jws = null;
                try {
                    //解析,解析方式使用加密时配置的数字签名对应
                    //一旦令牌修改成位数对比不上,会报错。。。
                    jws = Jwts.parser().setSigningKey("java521@java")
                            .parseClaimsJws(tokenStr.replace("Bearer", ""));
                    System.out.println(tokenStr.replace("Bearer", ""));
                      /*
                      打印结果 【不可换行,这里为了展示才换行】
                         eyJhbGciOiJIUzUxMiJ9.eyJhdXRob3JpdGllcyI6IlJPTEVfYWRtaW4sIiwic3ViIjoiYWRtaW4iLCJleHAiOjE
                         1OTE2MzAwMTF9.oHmTl-f5RetmFJ8rM5MaIruOkA83sqt-6F7f2c27QRWdJvTAOIYX_VbRCngodaROZ4jprQ1ktwz5sZAWcDJdkg
                      */
                } catch (Exception e) {
                    //放令牌被修改、时间过期,都会抛出异常,由方法 parseClaimsJws()安抛出的异常
    //                e.printStackTrace();
                    k = false;
                }
                if (k) {
                    // 令牌解析成功
                    Claims claims = jws.getBody();
                    //获取token解析出来的用户名
                    String username = claims.getSubject();
                    System.out.println(username);
                     /*
                      打印结果
                       [ROLE_admin,ROLE_user,等等]
                      */
                    //从token获取登录角色的权限
                    //如果时以逗号格式配置字符串,可用以下方式解析,否则手动解析
                    List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
                    System.out.println(grantedAuthorities);
                    //
    
                    //new令牌登录校验 对象,参数分别是  : 用户名 ,盐[没有则设为null] ,角色/权限
                    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
                    //执行令牌登录校验
                    SecurityContextHolder.getContext().setAuthentication(token);
                } else {
                    System.out.println("令牌解析失败,被修改了");
                    SecurityContextHolder.getContext()
                            .setAuthentication(new UsernamePasswordAuthenticationToken(null, null, null));
                }
            } else {
                System.out.println("没有认证令牌");
                SecurityContextHolder.getContext()
                        .setAuthentication(new UsernamePasswordAuthenticationToken(null, null, null));
            }
            System.out.println("//让过滤器继续往下走,");
            //让过滤器继续往下走
            filterChain.doFilter(servletRequest, servletResponse);
    
        }
    }
    
    
    /*
    总结:
    1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定
    2.生成的token是将登录通过的用户的权限拼接的字符串加密后放入里面后加密,当携带token访问时被拦截后,会将token解析出的权限注册,因为不与数据库等数据共享校验权限最新信息,
    如果在携带token的请求前权限有变化,但是token却没有改变,会导致token权限与用户真实权限不一致,形成脏数据啦!!!
    如果权限增加还好,使得无法访问新加权限的操作,如果是减少权限,比如vip过期,用户仍然可以有vip权限。
    3.解决token脏数据的方案有两个:
    (1)等待该token失效时间【不靠谱】;
    (2)每次修改权限时,会强制使得token失效,具体怎么做,还没试过
    4.当然,也有优点的,不与数据库等最新数据做权限对比操作,较少了访问数据库该用户信息的部分,能快速的过滤请求权限,理论上访问数据会变快。
    5.可以设置过期时间,单位毫秒,用时间戳设置 ,到时间则不可在使用,
    但是缺点很明显,在未过期之前,可以无数次访问验证通过,无法控制使用次数,
    因此不能作为资源服务器对第三方应用开放的授权令牌,
    6.令牌格式对不上,会直接报错异常,为了服务降级,做个异常捕获即可
    7.如果生成了新的令牌,旧的令牌仍然可以使用,因此会导致多设备同时登录的情况,无法控制登录数量
    8.使用jwt[java web token],做登录校验,则会导致http.sessionManagement().maximumSessions(1);设置失效,因为没有使用session做为登录控制
    //
    安全弊端很多 , 但是让我深刻明白了token的内部思想
     */
    View Code

    (6)服务层根据用户名获取用户信息【为了简便,没有使用数据库,直接赋值】

    package com.example.securityjwt5605.filters;
    
    
    import com.example.securityjwt5605.model.JwtUser;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.List;
    
    
    /**
     * 这个类其实就是为了获取用户的正确认证信息,不做信息比较,比较是在过滤器里面,
     * 名字叫做 UsernamePasswordAuthenticationFilter
     */
    
    @Service
    public class MyUserDetailsService implements UserDetailsService {
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            JwtUser tUser = new JwtUser();
            //权限设置
            List<GrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
            System.out.println("===============================数据库层对比==================================");
    
    
            if (username.equals("cen")) {
                tUser.setUsername(username);
                tUser.setPassword("$2a$10$Qghi7vHdyQJHYlAO.FCo/u3gCbqwWBVaSHjIF0Vci.C5.1l71SExq");
                simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_user"));
                tUser.setGrantedAuthorities(simpleGrantedAuthorities);
            } else if (username.equals("admin")) {
                tUser.setUsername(username);
                tUser.setPassword("$2a$10$ywq3gn6E15tnY3URptsIz.zn/fznWGqc2VhO4zphS/sIbWZJtLCVK");
                simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_admin"));
                tUser.setGrantedAuthorities(simpleGrantedAuthorities);
            } else {
                throw new UsernameNotFoundException("没有找到用户");
            }
    
            //        System.out.println("=============================");
    //        //根据用户名去数据库查询用户信息
    //        TUser tUser = userService.getByUsername(username);
    //        if (tUser == null){
    //            throw new UsernameNotFoundException("用户不存在!");
    //        }
    //        //权限设置
    //        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
    //        String role = tUser.getRole();
    //        //分割权限名称,如 user,admin
    //        String[] roles = role.split(",");
    //        System.out.println("=============================");
    //        System.out.println("注册该账户权限");
    //        for (String r :roles){
    //            System.out.println(r);
    //            //添加权限
    //            simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+r));
    ////            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(r));
    //        }
    //           tUser.setGrantedAuthorities(simpleGrantedAuthorities);
    //        System.out.println("=============================");
    
            /**
             * 创建一个用于认证的用户对象,包括:用户名,密码,权限
             *
             */
            //输入参数
    //        return new org.springframework.security.core.userdetails.User(tUser.getUsername(), tUser.getPassword(), simpleGrantedAuthorities);
    
    //        这个返回值的类型,继承了userdetails即可
            return tUser;
    
        }
    
    
    }
    View Code

    (7)contoller接口

    package com.example.securityjwt5605.controller;
    
    
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    public class HHController {
    
        //开启跨域
        // [普通跨域]
        //@CrossOrigin
        //[spring security 跨域]
        @CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
        @RequestMapping("/hello")
        public Map<String, Object> hello() {
            Map<String, Object> map = new HashMap<>();
            map.put("data", "hello");
            return map;
        }
    
        //开启跨域
        // [普通跨域]
        //@CrossOrigin
        //[spring security 跨域]
        @CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
        @RequestMapping("/admin")
        public Map<String, Object> admin() {
            Map<String, Object> map = new HashMap<>();
            map.put("data", "i am admin");
            return map;
        }
    
    
    }
    View Code

    (8)security配置类

    package com.example.securityjwt5605.config;
    
    
    import com.example.securityjwt5605.filters.JwtFilter;
    import com.example.securityjwt5605.filters.JwtLoginFilter;
    import com.example.securityjwt5605.filters.MyUserDetailsService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import java.lang.reflect.Method;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private MyUserDetailsService myUserDetailsService;
    
    
        /**
         * 全局的跨域配置
         */
        @Bean
        public WebMvcConfigurer WebMvcConfigurer() {
            return new WebMvcConfigurer() {
                public void addCorsMappings(CorsRegistry corsRegistry) {
                    //仅仅让/login可以跨域
                    corsRegistry.addMapping("/login").allowCredentials(true).allowedHeaders("*");
                    //仅仅让/logout可以跨域
                    corsRegistry.addMapping("/logout").allowCredentials(true).allowedHeaders("*");
                    //允许所有接口可以跨域访问
                    //corsRegistry.addMapping("/**").allowCredentials(true).allowedHeaders("*");
    
                }
            };
    
        }
    
        /**
         * 忽略过滤的静态文件路径
         */
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                    .antMatchers(
                            "/js/**/*.js",
                            "/css/**/*.css",
                            "/img/**",
                            "/html/**/*.html"
                    );
        }
    
    
        //内存放入可登录的用户信息
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            System.out.println("===============================认证管理构造器==================================");
    
            //直接注册信息到内存,会导致jrebel热更新失效,无法更新该内容
            //
            //如果仅仅设置了roles,则权限自动设置并自动添加前缀 为 ROLE_【角色内部的字符串,可以设置多个】,
            //字符串不可再添加ROLE_,会报java.lang.IllegalArgumentException: ROLE_user cannot start with ROLE_ (it is automatically added)
            //意思是用 ROLE_前缀会自动添加,
    //         auth.inMemoryAuthentication().withUser("cen")
    //                 .password("$2a$10$Qghi7vHdyQJHYlAO.FCo/u3gCbqwWBVaSHjIF0Vci.C5.1l71SExq").roles("user")
    //                //如果使用了roles 和 authorities ,那么roles将失效,将会注册authorities内部的字符串为权限,且不会添加前缀名ROLE_
    //                .and().withUser("admin")
    //                 .password("$2a$10$ywq3gn6E15tnY3URptsIz.zn/fznWGqc2VhO4zphS/sIbWZJtLCVK").roles("user").authorities("ROLE_admin");
    //            //
            //因此用户cen的权限为ROLE_user
            //用户admin的权限为 admin
    
            //
            //
            //调用数据库层,根据用户名获取用户信息回来,
            auth.userDetailsService(myUserDetailsService)
                    //设置加密方式
                    .passwordEncoder(passwordEncoder());
    
        }
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        //过滤规则,一旦设置了重写了这个方法,必须设置登录配置
        //在启动的时候就执行了
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            System.out.println("===============================过滤规则==================================");
    
            http.authorizeRequests()
                    .antMatchers("/hello").hasRole("user")
                    .antMatchers("/admin").hasRole("admin")
    //                .antMatchers("/admin").hasAuthority("admin")
                    //当访问/login的请求方式是post才允许通过
                    .antMatchers(HttpMethod.POST, "/login").permitAll()
    //                .anyRequest()
                    .anyRequest().authenticated()
                    .and()
                    //首次登录拦截。仅允许post访问/login
                    .addFilterBefore(new JwtLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                    //token验证拦截
                    .addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
                    //
                    .cors()
                    .and()
                    .csrf().disable();
            //
            //使用jwt[java web token],做登录校验,则该设置失效,因为没有使用session做为登录控制
    //        http.sessionManagement().maximumSessions(1);
    
    
        }
    
    
    }
    View Code

    (9)同时做了简易的前端访问页面【前后端分离,前端端口是5601 ,后端是5605】

    jwt.html

    <!DOCTYPE html>
    <html lang="zh" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>[JWT] 测试</title>
    </head>
    <body>
    jave web token [JWT] 测试
    <hr>
    <div>
        <label>
            账户:
            <input id="name" type="text">
        </label>
        <label>
            密码:
            <input id="psw" type="text">
        </label>
    </div>
    <button onclick="dosome(1)">登录</button>
    <hr>
    <hr>
    <button onclick="dosome(3)">登出</button>
    <hr>
    <label>
        token:
        <input id="url" type="text" value="http://localhost:5605/hello">
        <span id="token"></span>
    </label>
    <button onclick="dotoken()">点我token访问</button>
    <hr>
    返回的结果:<span id="res"></span>
    
    <!--当前路径是/html/**  ,因此需要返回一级 ,所以用  ../js/  -->
    <script src="../js/jquery-1.11.1.min.js"></script>
    <script src="../js/base64.js"></script>
    
    <script>
        //当前最新的token
        let token = "";
    
    
        function dotoken() {
            let url = ""+($("#url").val()).trim();
            if (url == ""){
                console.log("url不可空")
                return;
            }
            $.ajax({
                //请求头添加token
                //方法一:
                // beforeSend: function (request) {
                //     request.setRequestHeader("Authorization", token);
                // },
                //方法二:
                headers: {
                    //认证信息
                    Authorization: token
                },
                async: true,
                type: 'post',
                dataType: "json",
                url: url,
                xhrFields: {withCredentials: true},    //前端适配:允许session跨域
                crossDomain: true,
    
                success: function (data) {
                    console.log(data);
                    //请求成功回调函数
                    if (data != null) {
                        // alert("有数据返回")
                        $("#res").html(JSON.stringify(data))
                    } else {
                        alert("系统异常")
                    }
                },
                error: function (xhr, type, errorThrown) {
                    //异常处理;
                    console.log("异常处理")
                    console.log(JSON.stringify(xhr));
                    if (xhr.readyState == 4 && xhr.status == 403){
                        $("#res").html("403无权访问")
                    }
                    /*
                    {"readyState":4,"responseText":"{"timestamp":"2020-06-08T16:51:15.016+00:00",
                    "status":403,"error":"Forbidden","message":"","path":"/admin"}",
                    "responseJSON":{"timestamp":"2020-06-08T16:51:15.016+00:00","status":403,"error":"Forbidden",
                    "message":"","path":"/admin"},"status":403,"statusText":"error"}
                     */
                    console.log(type);
                    console.log(errorThrown);
                }
            });
        }
    
        function dosome(type) {
            let name = "";
            let psw = "";
            let url = "";
            if (type == 1) {
                name = ($("#name").val()).trim();
                psw = ($("#psw").val()).trim();
                //登录
                url = "http://localhost:5605/login";
            } else if (type == 3) {
                //登出
                url = "http://localhost:5605/logout";
            }
            //URL是URI的子集,所有的URL都是URI,但不是每个URI都是URL,还有可能是URN。
            $.ajax({
                async: true,
                type: 'post',
                //对应于后端 parama 方式获取数据 ,使用req.getParameter获取
                // data: {"username": name, "password": psw},
                //对应于后端raw方式获取数据,需要json解析,使用req.getInputStream()获取
                data: JSON.stringify({"username": name, "password": psw}),
                //这里类型是json,那么跨域的后端需要是map类型、po实体类等 json类型 才能接收数据
                dataType: "json",
                url: url,
                xhrFields: {withCredentials: true},    //前端适配:允许session跨域
                crossDomain: true,
                // //请求头设置
                // headers: {
                //     //认证信息
                //     Authorization: authorization
                // },
                success: function (data) {
                    console.log(data);
                    //请求成功回调函数
                    if (data != null) {
                        // alert("有数据返回")
                        $("#res").html(JSON.stringify(data))
                        token = data.token;
                        $("#token").html(token);
                    } else {
                        alert("系统异常")
                    }
                },
                error: function (xhr, type, errorThrown) {
                    //异常处理;
                    console.log("异常处理")
                    console.log(JSON.stringify(xhr));
                    console.log(type);
                    console.log(errorThrown);
                }
            });
        }
    
    </script>
    </body>
    </html>
    View Code

    3.测试

    token时间设长一点,我这里设为1小时

    (1)使用postman f访问  http://localhost:5605/login 进行登录

     登录成功

    获取令牌,访问  http://localhost:5605/hello

     提交后

     成功

    因为用户 cen我设置了只有权限。

    再次访问 http://localhost:5605/admin

     无权限403被拒绝了

    事实上,当令牌过期后再访问,也会抛出403结果

    换一个账户

     访问  http://localhost:5605/admin ,是可以访问的

     (2)测试前端 跨域 访问

    测试密码错误

     

    测试登录成功

    点击token访问

     token 成功

    换一个没有访问hello权限的账号

     然后再次点击token访问

     4.过滤器的先后操作

    (1)工程启动,控制台打印

    (2)用户名密码登录后,控制台打印

     (3)携带token访问

     有效的令牌

    无效的令牌

    奇怪的是 携带无效令牌时 会执行两次token访问过滤器,原因还不清楚

    -------------------------

    参考博文原址:

    https://mp.weixin.qq.com/s/Sn59dxwtsEWoj2wdynQuRQ

  • 相关阅读:
    Linux系统与网络服务管理技术
    RAM阵列
    5月9日上海书城PPT畅销图书作者讲座
    计算变为人们梦寐以求的公用设施
    博文视点大讲堂28期 “助你赢在软件外包行业”成功举办
    WebService WSDL详解(上)
    Google十三年
    预编译头sadafx.h原理
    WebService WSDL详解(下)
    Ext 2.2在IE 9运行居然说Extall.j运行错误,晕死了
  • 原文地址:https://www.cnblogs.com/c2g5201314/p/13070073.html
Copyright © 2020-2023  润新知