zoukankan      html  css  js  c++  java
  • SpringSecurity+Oauth2+Jwt实现toekn认证和刷新token

    简单描述:最近在处理鉴权这一块的东西,需求就是用户登录需要获取token,然后携带token访问接口,token认证成功接口才能返回正确的数据,如果访问接口时候token过期,就采用刷新token刷新令牌(得到新的token和refresh_token),然后在访问接口返回数据,如果刷新token也过期了,就提示用户重新登录。废话不多说,直接上代码。源码在github上

    使用 springboot + thymeleaf + mybatis 搭建的 

    //核心依赖
    <!-- spring security + OAuth2 + JWT    start  -->
    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.2.1.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- spring security + OAuth2 + JWT end -->
    //核心配置 application.yml文件
    #toekn相关配置
    token:
    config:
    #客户端标识,类比为token的用户名,我写的是项目名
    clientId: SOJ_DEMO
    #客户端安全码,类比为token的密码,我写的是假邮箱
    secret: soj@123
    #表示授权模式: password(密码模式),authorization_code(授权码模式)
    grantTypes: password
    #表示权限范围,该属性为可选项
    scopes: all
    #令牌的有效时长,此处为180s/60,时长为2分钟 设置短一点是为了测试刷新token
    accessTokenValidity: 120
    #刷新令牌的有效时长
    refreshTokenValidity: 36000
    #资源ID号
    resourceId: SOJ_DEMO
    #token签名的key,用于token对称加解密
    signingKey: SOJ_SYMMETRY
    #设置刷新令牌机制.true(重复使用:更新access_token时长后,refresh_toke时长不更新)。false(与true相反)
    isRefreshToken: false

    利用keytool工具生成密钥对(非对称秘钥 公钥私钥) 来执行签名过程  keytool工具使用帮助文档 这里得好好看看 最好一步步来 繁琐的过程,我的妈呀 真的要吐了 太恶心了

    //在cmd命令提示符中执行此命令  绿色部分是变量 你可以修改
    keytool -genkeypair -alias jwt -keyalg RSA -keypass xc1234 -keystore jwt.jks -storepass xc1234 

     执行完之后会在C:UsersAdministrator目录下生成一个jwt.jks文件, .jks文件包含了我们的秘钥 公钥 私钥,后续会在代码中读取此文件中的公钥私钥。

     把生成的jwt.jks文件放到resources目录下的certificate文件夹中,这里给一下目录结构

     然后在POM文件中加入对此文件的引入

    //pom文件

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>

    <resources>
    <resource>
    <directory>src/main/resources</directory>
    <filtering>true</filtering>
    <excludes>
    <exclude>certificate/*.jks</exclude>
    </excludes>
    </resource>
    <resource>
    <directory>src/main/resources</directory>
    <filtering>false</filtering>
    <includes>
    <include>certificate/*.jks</include>
    </includes>
    </resource>
    </resources>
    </build>

     下边开始代码

    JwtToken主要负责token解析和加解密 和上边的jwt.jks文件打交道 读取公钥私钥

    package com.xc.soj_demo.jwt;
    
    import io.jsonwebtoken.ClaimJwtException;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.context.annotation.Configuration;
    
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.util.Date;
    
    /**
     * token加解密
     * 
     * 1.对称加解密
     * 2.非对称加解密(RSA)
     * 
     */
    @Configuration
    public class JwtToken {
        
        /**
         * 加载jwt.jks文件
         */
        private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("certificate/jwt.jks");
        private static PrivateKey privateKey = null;
        private static PublicKey publicKey = null;
    
        static {
            try {
                KeyStore keyStore = KeyStore.getInstance("JKS");
                keyStore.load(inputStream, "xc1234".toCharArray());
                privateKey = (PrivateKey) keyStore.getKey("jwt", "xc1234".toCharArray());
                publicKey = keyStore.getCertificate("jwt").getPublicKey();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        /**
         * 生成jwt token(非对称加密模式 公钥私钥)
         */
        public static String generateTokenRSA(String subject, int expirationSeconds) {
            Date nowDate = new Date();
            Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
            return Jwts.builder()
                    .setClaims(null)
                    .setHeaderParam("typ", "JWT")
                    .setSubject(subject)
                    .setIssuedAt(nowDate)
                    .setExpiration(expireDate)
                    .signWith(SignatureAlgorithm.RS256, privateKey) 
                    .compact();
        }
    
        /**
         * 解析jwt token(非对称加密模式 公钥私钥)
         */
        public static Claims parseTokenRSA(String token) {
            if (StringUtils.isEmpty(token)) {
                return null;
            }
      
            try {
                return Jwts.parser()
                        .setSigningKey(publicKey)
                        .parseClaimsJws(token)
                        .getBody();
            }catch (Exception e){
                System.out.println("try-catch:validate is token error ");
                return null;
            }
        }
        
        /**
         * token是否过期
         * @return  true:过期
         */
        public static boolean isTokenExpired(Date expiration) {
            boolean before = expiration.before(new Date());
            return before;
        }
        
        /**
         * 生成jwt token(对称加密模式)
         */
        public static String generateToken(String subject, int expirationSeconds,
                String signingKey) {
            Date nowDate = new Date();
            Date expireDate = new Date(nowDate.getTime() + expirationSeconds * 1000);
            return Jwts.builder()
                    .setHeaderParam("typ", "JWT")
                    .setSubject(subject)
                    .setIssuedAt(nowDate)
                    .setExpiration(expireDate)
                    .signWith(SignatureAlgorithm.HS512, signingKey)
                    .compact();
        }
        
        /**
         * 解析jwt token(对称加密模式)
         */
        public static String parseToken(String token,String signingKey) {
            if (StringUtils.isEmpty(token)) {
                return null;
            }
            token = StringUtils.substringAfter(token, "bearer");//定义token令牌的类型为bearer
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(signingKey.getBytes("UTF-8")).parseClaimsJws(token).getBody();
            } catch (ClaimJwtException e) {
                //源码DefaultJwtParser.Class中的处理过程是 从token中取出载荷payload部分,解析出claim(claim中存在用户信息),然后在解析是否过期,最后才抛出的异常
                //所以是可以从 ClaimJwtException e中取出需要的部分 并且源码ClaimJwtException.Class类中有header和claim两个私有属性并提供了get方法
                claims = e.getClaims();
            } catch (UnsupportedEncodingException e) {
                return null;
            }
            String localUser = (String) claims.get("userinfo");// 拿到当前用户
            return localUser;
        }
        
        
    }
    JwtToken.java

     AuthServerConfig主要负责配置令牌加载的属性,自定义用户信息到token令牌内

    package com.xc.soj_demo.authenticationConfig;
    
    import com.alibaba.fastjson.JSON;
    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.authentication.AuthenticationManager;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
    import org.springframework.security.oauth2.common.OAuth2AccessToken;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * OAuth2配置类
     * 
     * 1.配置令牌加载的属性
     * 2.自定义用户信息到token令牌内
     * 
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private TokenConfig tokenConfig;
     
        /**
         * 注入authenticationManager
         * 来支持 password grant type
         */
        @Autowired
        private AuthenticationManager authenticationManager;
    
        /**
         * 注入userDetailService
         * 来支持 refresh_token grant type
         * 人话讲 就是toekn失效 需要用到refresh_token去重新请求 /oauth/token来签发新的token和refresh_token
         */
        @Autowired
        private UserDetailsService userDetailService;
     
        /**
         * 定义oauth/token类接口信息
         * 
         * @description tokenConfig map
         *        map.get("clientId")    类比为token的用户名
         *        map.get("secret")    类比为token的密码
         *      map.get("grantTypes")表示授权类型 grant_type: password(密码模式)
         *      map.get("scopes")权限范围
         *      map.get("accessTokenValidity")token有效期
         *      map.get("refreshTokenValidity")刷新token有效时间
         *      map.get("resourceId")定义资源令牌头部,资源服务器验证令牌时用到
         *  
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            
            Map<String, String> map = tokenConfig.getConfig();
            clients.inMemory()
                    .withClient(map.get("clientId"))
                    .secret("{noop}" + map.get("secret"))
                    .authorizedGrantTypes(map.get("grantTypes"), "refresh_token")
                    .scopes(map.get("scopes"))
                    .accessTokenValiditySeconds(Integer.parseInt(map.get("accessTokenValidity"))) 
                    .refreshTokenValiditySeconds(Integer.parseInt(map.get("refreshTokenValidity")))
                    .resourceIds(tokenConfig.getResourceId())
    //                .authorities("ADMIN")
    //                .redirectUris("http://localhost:8882/login") // 认证成功重定向URL
                    .autoApprove(true);// 自动认证
            
        }
     
        /**
         * token令牌配置
         * 
         * @description 1.定义自定义token生成方式、tokenStore、、认证管理器
         *                 2.定义token加解密转换器
         *                 3.定义token请求方式
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.accessTokenConverter(accessTokenConverter());
            endpoints.authenticationManager(authenticationManager);
            endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
            endpoints.userDetailsService(userDetailService);//支持refresh_token机制
            endpoints.reuseRefreshTokens(tokenConfig.isRefreshToken());//和配置文件对应的 具体看application.yml最后一项
        }
     
        /**
         * OAuth2服务配置
         * 
         * @description 1.允许/oauth/token被调用,默认deny
         *                 2.允许所有检查token,默认deny。必须加,否则check_token不能访问显示401未授权错误
         *                 3.允许表单认证
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer
    //            .tokenKeyAccess("permitAll()")
    //            .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
        }
     
        /**
         * 生成jwt令牌
         * @return
         */
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
                /***
                 * 重写增强token方法,用于自定义一些token总需要封装的信息
                 * @return
                 */
                @Override
                public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    
                    Authentication user = authentication.getUserAuthentication();
                    String userName = user.getName();
                    Collection<? extends GrantedAuthority> authority = user.getAuthorities();
                    // 得到用户名,去处理数据库可以拿到当前用户的信息和角色信息(需要传递到服务中用到的信息)
                    final Map<String, Object> additionalInformation = new HashMap<>();
                    // Map假装用户实体
                    Map<String, Object> userinfo = new HashMap<>();
                    userinfo.put("userId", "001");
                    userinfo.put("username", userName);
                    userinfo.put("authOrity", authority);
                    additionalInformation.put("userinfo", JSON.toJSONString(userinfo));
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                    OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
                    return enhancedToken;
                }
            };
            // 生成签名的key,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式
            accessTokenConverter.setSigningKey(tokenConfig.getSigningKey());
            return accessTokenConverter;
        }
        
        
    }
    AuthServerConfig.java

     OauthInterceprtor负责拦截oauth的异常,主要是token过期之后的处理,采用刷新令牌机制重新获取token,再次请求资源

    package com.xc.soj_demo.authenticationConfig;
    
    import com.xc.soj_demo.constant.CodeConstant;
    import com.xc.soj_demo.dao.UserDao;
    import com.xc.soj_demo.entity.User;
    import com.xc.soj_demo.jwt.JwtToken;
    import com.xc.soj_demo.util.JsonUtil;
    import net.sf.json.JSONObject;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.http.ResponseEntity;
    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.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
    import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
    import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.*;
    
    /**
     * OAuth2异常拦截类
     *
     * 1.对oauth错误异常进行拦截,这里主要针对令牌过期进行处理
     * 2.新的令牌与刷新令牌的存储
     * 3.载入用户信息到spring Security的ContextHolder中,保证后续url转发
     * 4.刷新令牌过期后的返回状态
     *
     */
    public class OauthInterceptor extends OAuth2AuthenticationEntryPoint {
    
        @Value("${server.port}")
        private String port;
    
        @Autowired
        private TokenConfig tokenConfig;
    
        /**
         * 在启动类中注入了restTemplate Bean
         */
        @Autowired
        RestTemplate restTemplate;
    
        @Resource
        private UserDao dao;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            try {
                ResponseEntity<?> result = exceptionTranslator.translate(authException);
                JSONObject objBody = JSONObject.fromObject(result.getBody());
                String message = objBody.getString("message");
    
                //判断是否为"访问令牌过期",如果不是则以默认的方法继续处理其他异常
                if (message.contains("Access token expired")) {
    
                    //根据访问令牌,解析出当前令牌用户的用户名称,密码等信息
                    String localUser = JwtToken.parseToken(request.getHeader("Authorization"), tokenConfig.getSigningKey());
                    @SuppressWarnings("unchecked")
                    Map<String, Object> userMap = (Map<String, Object>) JsonUtil.json2Map(localUser);
                    String username = (String)userMap.get("username");
    
                    //根据用户名称,从数据库获取用户的刷新令牌
                    String refresh_token = dao.getRefreshToken(username);
    
                    //获取当前用户信息
                    User userObj  = dao.getUserByUserName(username);
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", 1);//用户存在 密码正确
                    map.put("userId", userObj.getUserId());
                    map.put("username", userObj.getUsername());
                    map.put("password", userObj.getPassword());
                    List<String> listPermission = new ArrayList<>();
                    listPermission.add("user::add");
                    listPermission.add("user::list");
                    listPermission.add("user::update");
                    listPermission.add("user::delete");
    
                    List<String> listRole = new ArrayList<>();
    //                listRole.add("sys_admin");
    //                listRole.add("admin");
                    listRole.add(userObj.getUserRole());
    
                    map.put("authOrity", listRole);
                    map.put("userPermission", listPermission);
    
                    //获取OAuth2框架的配置信息,用于访问刷新令牌接口
                    Map<String, String> tokenMap = tokenConfig.getConfig();
                    Map<String,String> mapParam = new HashMap<>();
                    mapParam.put("username", userObj.getUsername());
                    mapParam.put("password", userObj.getPassword());
                    mapParam.put("client_id", tokenMap.get("clientId"));
                    mapParam.put("client_secret", tokenMap.get("secret"));
                    mapParam.put("grant_type", "refresh_token");//这里没有写错 采用刷新令牌的方式
                    mapParam.put("refresh_token", refresh_token);
                    try {
    
                        @SuppressWarnings("unchecked")
                        Map<String, String> mapResult = restTemplate
                                .getForObject(
                                        "http://localhost:"+port+"/oauth/token?username={username}&password={password}&client_id={client_id}&client_secret={client_secret}&grant_type={grant_type}&refresh_token={refresh_token}",
                                        Map.class, mapParam);
                        // 如果刷新成功 跳转到原来需要访问的页面
                        //写入用户信息到公共变量中,写入信息到SecurityContext中
                        CodeConstant.USER_MAP = map;
                        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
                        for (String role : listRole) {
                            grantedAuthorityList.add(new SimpleGrantedAuthority(
                                    role));
                        }
                        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                                userObj.getUsername(), userObj.getPassword(), grantedAuthorityList);
                        Authentication authentications = authenticationManager
                                .authenticate(authRequest);
                        SecurityContextHolder.getContext().setAuthentication(
                                authentications);
    
                        response.setHeader("access_token",
                                mapResult.get("access_token"));
    //                    response.setHeader("refresh_token",
    //                            mapResult.get("refresh_token"));
    
                        //把新获取到的refresh_token存到数据库
                        dao.setRefreshToken(userObj.getUserId(),mapResult.get("access_token"));
                        response.setHeader("isRefreshToken", "yes");
                        request.getRequestDispatcher(request.getRequestURI())
                                .forward(request, response);
                    } catch (Exception e) {
    //                    e.printStackTrace();
                        // 获取刷新令牌失败时(刷新令牌过期时),返回指定格式的错误信息
                        response.setHeader("Content-Type", "application/json;charset=utf-8");
                        response.getWriter().print("{"code":411,"message":"刷新令牌以过期,需要重新登录."}");
                        response.getWriter().flush();
                    }
                }else{
                    super.commence(request,response,authException);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    OauthInterceptor.java

     ResourceConfiguration资源服务器配置,具体负责后台 哪些接口放开,哪些接口拦截

    package com.xc.soj_demo.authenticationConfig;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    
    /**
     * 资源服务器-配置类
     * 
     * 1.设置接口访问权限
     * 2.token验证
     * 
     */
    @Configuration
    @EnableResourceServer
    public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
        
        @Value("${token.resourceId}")
        private String resourceId;
     
        /**
         * 定义资源服务器接口访问权限
         * 
         * @description 1.定义无权限接口
         *                 2.定义接口访问权限为admin
         *                 3.定义接口访问权限为sys_admin
         */
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/order/*","/getToken","/parseToken","/sys/test","/sys/login","/sys/doLogin","/js/**").permitAll()// "/order/*"资源是开放的
                .and().authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
    //            .antMatchers("/B").hasRole("admin")
    //          .antMatchers("/admin").hasRole("sys_admin")
                .anyRequest().authenticated();
    
        }
        
        /**
         * 定义资源服务器解析协议表头(需要与认证服务器定义的表头一致)
         */
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(resourceId).stateless(true);
            resources.authenticationEntryPoint(new OauthInterceptor());
        }
    
    
    }
    ResourceConfiguration.java

    TokenConfig从配置文件读取token的相关配置

    package com.xc.soj_demo.authenticationConfig;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.HashMap;
    import java.util.Map;
    
    
    @Component
    @Data
    @ConfigurationProperties(prefix="token")
    public class TokenConfig {
        
        private Map<String, String> config = new HashMap<>();
        private String resourceId;
        private String signingKey;
        private boolean isRefreshToken;
    
    }
    TokenConfig.java

    UserDetailService 用户信息获取

    package com.xc.soj_demo.authenticationConfig;
    
    import com.xc.soj_demo.constant.CodeConstant;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    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.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 用户信息获取(用户名称,密码,权限)
     * 
     */
    @Component
    public class UserDetailService implements UserDetailsService {
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //这个地方可以通过username从数据库获取正确的用户信息,包括密码和权限等。
            // 从user获取正确的用户信息,包括密码和权限等。
            Map<String, Object> user = CodeConstant.USER_MAP;
            if (user != null) {
                @SuppressWarnings("unchecked")
                List<String> authOrity = (List<String>) user.get("authOrity");
                String PASSWORD = "{noop}" + user.get("password").toString();
                List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
                for (String auth : authOrity) {
                    grantedAuthorityList.add(new SimpleGrantedAuthority(auth));
                }
                return new User(username, PASSWORD, grantedAuthorityList);
            } else {
                throw new UsernameNotFoundException("用户[" + username + "]不存在");
            }
    
        }
        
        
    }
    UserDetailService.java

    WebSecurityConfig认证服务器配置

    package com.xc.soj_demo.authenticationConfig;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    /**
     * Security-配置类(认证服务器)
     * 
     * 1.配置请求URL的访问策略
     * 2.自定义认证登录页面URL
     * 3.配置OAuth2密码模式
     * 
     */
    @EnableWebSecurity//开启权限验证
    @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)//通过表达式控制方法权限
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     
        /**
         * 配置访问策略
         * 
         * @description 1.设置授权请求
         *                 2.自定义登录界面
         *                 3.设置使用jwt,可以允许跨域
         */
         @Override
         protected void configure(HttpSecurity http) throws Exception {
                http.requestMatchers()
                    .antMatchers("/login")
                    .antMatchers("/oauth/**")
                    .and().authorizeRequests()
                    .anyRequest().authenticated()
                    .and().formLogin().loginPage("/login").permitAll()
                    .and().csrf().disable();
         }
         
        /**
         * 需要配置这个支持password模式 support password grant type
         * @return
         * @throws Exception
         */
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
     
        
    }
    WebSecurityConfig.java

    核心代码到这里就结束了,下边是业务代码,和上边的关联起来 MVC那一套

    常量类:

    package com.xc.soj_demo.constant;
    
    import java.util.Map;
    
    public class CodeConstant {
        //为了减少查询数据库的次数,把用户的一些信息暂时存放到这里
        public static Map<String, Object> USER_MAP = null;
    
    }

    Controller:

    package com.xc.soj_demo.controller;
    
    import com.xc.soj_demo.entity.User;
    import com.xc.soj_demo.service.UserService;
    import com.xc.soj_demo.util.JsonUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    
    @Controller
    @RequestMapping("/sys")
    public class LoginController {
    
        private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    
        @Resource
        private UserService userService;
    
        //测试页面
        @RequestMapping("/test")
        public String testThymeleaf(ModelMap model) {
            User user = new User();
            user.setUsername("盖聂");
            user.setUserRole("大叔");
            model.addAttribute("user", user);
            return "/viewTest";
        }
    
        //登录页面
        @RequestMapping("/login")
        public String login(ModelMap model) {
            return "/login";
        }
    
        @RequestMapping(value = "/doLogin")
        @ResponseBody
        public String login(User user) {
            Map<String, Object> resultMap = userService.login(user.getUsername(), user.getPassword());
            String str = JsonUtil.map2Json(resultMap);
            logger.info(str);
            return str;
        }
    
    
        //测试页面
        @RequestMapping("/getList")
        @ResponseBody
        public List<String> getList() {
            List<String> list = new ArrayList<>();
            list.add("aa");
            list.add("bb");
    
            return list;
        }
    }
    LoginController.java

    Dao:

    package com.xc.soj_demo.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    import com.xc.soj_demo.entity.User;
    
    import java.util.Map;
    
    @Mapper
    public interface UserDao {
        User getUserByUserName(String username);
    
        void addToken2User(Map<String, Object> map);
    
        void setRefreshToken(String userId, String refreshToken);
    
        String getPasswordByUserName(String username);
    
        String getRefreshToken(String username)throws Exception;
    
        String selectRefreshTokenByUserId(Integer userId);
    }

    实体类:

    package com.xc.soj_demo.entity;
    
    
    import lombok.Data;
    
    @Data
    public class User {
    
        private String userId;
        private String username;
        private String password;
        private String userPermission;
        private String userRole;
        private String token;
        private String refreshToken;
    
    }
    User.java

    实现类:

    package com.xc.soj_demo.service.impl;
    
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.xc.soj_demo.authenticationConfig.TokenConfig;
    import com.xc.soj_demo.constant.CodeConstant;
    import com.xc.soj_demo.dao.UserDao;
    import com.xc.soj_demo.entity.User;
    import com.xc.soj_demo.service.UserService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Service;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.client.RestTemplate;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
        private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
        @Value("${server.port}")
        private String port;
    
        @Autowired
        private UserDao dao;
    
        @Autowired
        HttpServletRequest request;
    
        @Autowired
        RestTemplate restTemplate;
    
        @Autowired
        private TokenConfig tokenConfig;
    
        @Override
        public Map<String, Object> login(String username, String password) {
            Map<String, Object> map = new HashMap<>();
            User userObj = dao.getUserByUserName(username);
            // 判断用户是否存在
            if (null == userObj) {
                map.put("code", 1);// 用户不存在
                return map;
            }
            // 判断密码是否正确
            if (!password.equals(userObj.getPassword())) {
                map.put("code", -1);// 密码错误
                return map;
            }
            map.put("code", 0);// 用户存在 密码正确
            map.put("userId", userObj.getUserId());
            map.put("username", userObj.getUsername());
            String userId = userObj.getUserId();
    
    //        List<String> listAuthority = dao.getUserPermissionByUserid(userId);
            //模拟 查询用户权限(查询结果 可以对用户进行增删改查)
            List<String> listPermission = new ArrayList<>();
            listPermission.add("user::add");
            listPermission.add("user::list");
            listPermission.add("user::update");
            listPermission.add("user::delete");
    
    //        List<String> listRole = dao.getUserRolesByUid(userId);
            //模拟 查询用户角色 (查询结果 userObj具有系统管理员 普通管理员的角色)
            List<String> listRole = new ArrayList<>();
    //        listRole.add("sys_admin");
    //        listRole.add("admin");
            listRole.add(userObj.getUserRole());
    
            map.put("authOrity", listRole);
            map.put("userPermission", listPermission);
            map.put("password", password);
            try {
                map.put("access_token", getOAuthToken(map));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return map;
        }
    
        /**
         * 调用OAuth2的获取令牌接口
         *
         * @description 1.将用户信息存入公共map中 2.获取访问令牌 3.写入"刷新令牌"到数据库
         *
         */
        private String getOAuthToken(Map<String, Object> map) throws JsonProcessingException {
            CodeConstant.USER_MAP = map;
    
            Map<String, String> tokenMap = tokenConfig.getConfig();
            MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
            formData.add("username", map.get("username").toString());
            formData.add("password", map.get("password").toString());
            formData.add("client_id", tokenMap.get("clientId"));
            formData.add("client_secret", tokenMap.get("secret"));
            formData.add("grant_type", tokenMap.get("grantTypes"));
    
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    
            String urlStr = "http://localhost:" + port + "/oauth/token";
            Map<?, ?> resultMap = restTemplate.exchange(urlStr, HttpMethod.POST,
                    new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
    
            if (null != resultMap) {
                try {
                    setRefreshToken(map.get("userId").toString(), resultMap.get("refresh_token").toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return resultMap.get("access_token").toString();
            }
            return null;
        }
    
        /**
         * 更新用户的刷新令牌
         *
         */
        public void setRefreshToken(String userId, String refreshToken) throws Exception {
            dao.setRefreshToken(userId, refreshToken);
        }
    
        public String getPasswordByUserName(String username) throws Exception {
            String password = dao.getPasswordByUserName(username);
            return password;
        }
    
    
        /**
         * @Description 根据用户ID获取刷新token
         * @param userId
         * @param refreshToken
         * @return Boolean
         * @author chao.song
         */
        public Boolean verdictRefreshTokenByUId(Integer userId, String refreshToken) {
            if (userId == null || userId < 0) {
                return false;
            }
            if (refreshToken.isEmpty()) {
                return false;
            }
            String baseRefreshToken = dao.selectRefreshTokenByUserId(userId);
            if (refreshToken.equals(baseRefreshToken)) {
                return true;
            }
            return false;
        }
    
    
    }
    UserServiceImpl.java

    Json工具类

    package com.xc.soj_demo.util;
    
    
    import com.fasterxml.jackson.core.JsonParser.Feature;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.io.IOException;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    
    public class JsonUtil {
    
        private static final ObjectMapper mObjectMapper = new ObjectMapper();
    
        static {
            mObjectMapper.configure(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
            mObjectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
            mObjectMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
            mObjectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
            mObjectMapper.configure(Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
            mObjectMapper.configure(Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
    
            mObjectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
            mObjectMapper.configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
            mObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
            mObjectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            mObjectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
    
            DateFormat myDateFormat = new SimpleDateFormat("yyyy-MM-DD hh:mm:ss");
            mObjectMapper.getSerializationConfig().with(myDateFormat);
            mObjectMapper.getDeserializationConfig().with(myDateFormat);
        }
    
        /**
         * parameters key
         */
        private static final String PARA_KEY = "parameters";
    
        /**
         * @param jsonString
         * @return
         */
        public static Map<?, ?> json2Map(String jsonString) {
            try {
                Map<?, ?> map = mObjectMapper.readValue(jsonString, Map.class);
                return map;
            } catch (JsonMappingException e) {
                e.printStackTrace();
                return null;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param jsonString
         * @return
         * @description
         */
        public static List<Map<?, ?>> json2MapOfArrayList(String jsonString) {
            try {
                JavaType javaType = getCollectionType(ArrayList.class, Map.class);
                @SuppressWarnings("unchecked")
                List<Map<?, ?>> arrayList = (List<Map<?, ?>>) mObjectMapper.readValue(jsonString, javaType);
    
                return arrayList;
            } catch (JsonMappingException e) {
                e.printStackTrace();
                return null;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param collectionClass
         * @param elementClasses
         * @return
         */
        public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
            return mObjectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
        }
    
        /**
         * @param map
         * @return
         * @description map to json string
         */
        public static String map2Json(Map<?, ?> map) {
            try {
                String ret = "";
                ret = mObjectMapper.writeValueAsString(map);
                // remove all ""
                ret = ret.replaceAll("\\", "");
                if (ret.contains(""[")) {
                    ret = ret.replaceAll(""\[", "\[");
                }
                if (ret.contains("]"")) {
                    ret = ret.replaceAll("\]"", "\]");
                }
                if (ret.contains(""{")) {
                    ret = ret.replaceAll(""\{", "\{");
                }
                if (ret.contains("}"")) {
                    ret = ret.replaceAll("\}"", "\}");
                }
                return ret;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param list
         * @return
         */
        public static String listMap2Json(List<Map<String, Object>> list) {
            try {
                String ret = "";
                ret = mObjectMapper.writeValueAsString(list);
                // remove all ""
                ret = ret.replaceAll("\\", "");
                return ret;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param strContent
         * @return
         * @description
         */
        public static String getJsonString(String strContent) {
            String ret = "";
    
            if (!strContent.contains(PARA_KEY)) {
                return ret;
            }
            String regex = "parameters[\s]+=";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(strContent);
            if (m.find()) {
                strContent = strContent.replaceFirst(regex, "");
            } else {
                regex = "parameters=";
                p = Pattern.compile(regex);
                m = p.matcher(strContent);
                if (m.find()) {
                    strContent = strContent.replaceFirst(regex, "");
                }
            }
            strContent = strContent.trim();
            return strContent;
        }
    
        /**
         * @param byteArray
         * @return
         * @description
         */
        public static String bytes2Hex(byte[] byteArray) {
            StringBuffer strBuf = new StringBuffer();
            for (int i = 0; i < byteArray.length; i++) {
                if (byteArray[i] >= 0 && byteArray[i] < 16) {
                    strBuf.append("0");
                }
                strBuf.append(Integer.toHexString(byteArray[i] & 0xFF));
            }
            return strBuf.toString();
        }
    
        /**
         * method_name: mapFormatString2List
         * <p>
         * parameters: mapString format is: [ { id=1, time=2013-11-09 09:00:00 }, {
         * id=2, time=2013-11-10 09:00:00 } ]
         * <p>
         * <p>
         * return: List<Map<String, Object>>
         */
        public static List<Map<String, Object>> mapFormatString2List(String strContent) {
            List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
            String ret = "";
            String regex = "\{[^}]+\}"; // \{[^}]+\} {[^}]*}
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(strContent);
            while (m.find()) {
                ret = m.group();
                ret = ret.replaceAll("\{", "");
                ret = ret.replaceAll("\}", "");
                ret = ret.trim();
                Map<String, Object> map = transStringToMap(ret);
                list.add(map);
            }
            return list;
        }
    
        /**
         * method_name: transStringToMap
         * <p>
         * parameters: mapString format is: id=1, time=2013-11-09 09:00:00 (delim:",",
         * token:"=")
         * <p>
         * return: Map
         */
        public static Map<String, Object> transStringToMap(String mapString) {
            Map<String, Object> map = new HashMap<String, Object>();
            StringTokenizer items;
            for (StringTokenizer entrys = new StringTokenizer(mapString, ","); entrys.hasMoreTokens(); map
                    .put(items.nextToken().trim(), items.hasMoreTokens() ? ((Object) (items.nextToken().trim())) : null)) {
                items = new StringTokenizer(entrys.nextToken(), "=");
            }
            return map;
        }
    
        /**
         * @param str
         * @return
         * @description trim
         */
        public static String trimAll(String str) {
            if (null == str || str.length() <= 0) {
                return str;
    
            } else {
                return str.replaceAll("^[ ]+|[ ]+$", "");
            }
        }
    
    }
    JsonUtil.java

    mapper文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xc.soj_demo.dao.UserDao">
        <resultMap id="UserResultMap" type="com.xc.soj_demo.entity.User">
            <id property="userId" column="user_id"/>
            <result property="username" column="username"/>
            <result property="password" column="password"/>
            <result property="userPermission" column="user_permission"/>
            <result property="userRole" column="user_role"/>
            <result property="token" column="token"/>
            <result property="refreshToken" column="refresh_token" />
        </resultMap>
    
        <sql id="tableName">t_user</sql>
    
        <update id="addToken2User" parameterType="java.util.Map">
            update
            <include refid="tableName"/>
            set token = #{token} where user_id = #{userId}
        </update>
    
        <select id="getUserByUserName" parameterType="String" resultMap="UserResultMap">
            select * from
            <include refid="tableName"/>
            where username = #{username}
        </select>
    
        <!-- 获取用户密码 -->
        <select id="getPasswordByUserName" parameterType="String" resultType="String">
            select password from <include refid="tableName" /> where username = #{username}
        </select>
    
        <!-- 更新刷新令牌 -->
        <update id="setRefreshToken">
            update <include refid="tableName" /> set refresh_token = #{refreshToken} where user_id = #{userId}
        </update>
    
        <!-- 获取用户的刷新令牌 -->
        <select id="getRefreshToken" parameterType="String" resultType="String">
            select refresh_token from <include refid="tableName" />  where username = #{username}
        </select>
    
    
        <select id="selectRefreshTokenByUserId" parameterType="Integer" resultType="String">
            select refresh_token from <include refid="tableName" /> where user_id = #{userId}
        </select>
    </mapper>
    UserMapper.xml

    记得在主启动类中把restTemplate注入进去,这个是要用到的

    测试:

    首先登陆获取token

     拿到token后请求后台接口getList

     然后 等2分钟  等token过期 再次请求getList

     

     可以看到 第一次使用token去请求的时间是 15:21:26  等token过期之后再请求的时间是15:24:32  在配置文件中配置的token有效期是2分钟 刷新token时间长一点是10个小时,这时候请求也可以访问的原因  后台的刷新tokne机制起了作用。请求接口的时候使用的token是紫色方框中的那个后台验证 这个token已过期  然后刷新token机制开始工作。从已失效的tokne的载荷中 取出用户名,然后根据用户名查询用户信息,得到refresh_token,然后使用refresh_token请求/oauth/token 获取新的tokne也就是绿色方框中的access_token,最后在接着访问接口的url的到结果返回回来。刷新token机制 具体体现在OauthInterceptor.java中

  • 相关阅读:
    while 循环 。。
    数据运算,运算符
    字符串常用操作
    列表常用操作
    三级菜单
    杂七杂八
    简单的登陆程序001
    猜年龄游戏
    实现密文输入密码
    使用urllib2打开网页的三种方法
  • 原文地址:https://www.cnblogs.com/xuchao0506/p/13073913.html
Copyright © 2011-2022 走看看