1. 程式人生 > >基於token的登入管理(多裝置登入、單裝置登入)

基於token的登入管理(多裝置登入、單裝置登入)

不管是客戶端介面還是網頁H5介面,一般我們都需要登入驗證,即要求所有的介面訪問都必須在登入之後,以確認身份,防止非法呼叫。一般的流程都是登入的時候返回一個代表此登入的token,以後所有介面都帶上此token,在所有介面呼叫之前攔截驗證,一般都是通過AOP或者一個Filter、攔截器來實現。而退出的時候呼叫介面將此token刪除即可。一般地,為了對介面侵入最小,能做到統一處理,可以將此token放在header中。token一般都會設定一個有效期,過期了直接提示呼叫者需要登入以控制條轉到登入頁面引導登入。

服務端設計:

/**
 * token管理器
 * @author xiongshiyan
 */
public interface TokenManager<M> {
    /**
     * 生成token
     * @param m 實體
     * @return token值
     */
    String createToken(M m);
    String createToken(M m , long expires);

    /**
     * 根據token獲取
     * @param token token
     * @return 根據token獲取的實體
     */
    M findByToken(String token);

    /**
     * 更新token的過期
     * @param token token
     */
    void updateExpires(String token);
    void updateExpires(String token  , long expires);

    /**
     * 刪除
     * @param token token
     * @return 刪除是否成功
     */
    boolean deleteToken(String token);

    /**
     * 產生token
     * @param m 實體
     * @return token
     */
    String getToken(M m);
}

M代表登入實體,也可以是能代表登入人的唯一標識,使用泛型指定。createToken用於生成並儲存token,findByToken用於通過token找到登入實體,updateExpires用於更新token的過期時間,deleteToken用於刪除token(登入退出的時候),getToken生成token字串。一般驗證token是幾乎每個介面都會用,所以必須保證速度,可以採用redis來儲存。

/**
 * 基於redis的token管理器基類
 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected]
or phone 15208384257 */ public abstract class AbstractRedisTokenManager<M> implements TokenManager<M> { protected RedisUtil redisUtil; public AbstractRedisTokenManager(RedisUtil redisUtil){ this.redisUtil = redisUtil; } @Override public String createToken(M m, long expires) { String token = getToken(m); redisUtil.set(token , m , expires); return token; } @Override public M findByToken(String token) { if(null == token){ return null; } Object o = redisUtil.get(token); if(null == o){ return null; } return (M) o; } @Override public void updateExpires(String token , long expires){ if(null == token){ return; } redisUtil.expire(token , expires); } @Override public boolean deleteToken(String token){ Object o = redisUtil.get(token); if(null == o){ return false; } redisUtil.del(token); return true; } }

此基類實現了一些公共的方法,繼承此類實現剩餘的方法即可。

1.允許多裝置登入的實現。這種情形下,token可以隨意生成,只要保證不重複即可。

/**
 * 客戶端API介面token管理器【支援多裝置登入】
 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257
 */
public class ApiTokenManager extends AbstractRedisTokenManager<Member> {
    private String apiTokenPrefix;
    private long apiExpires;

    public ApiTokenManager(RedisUtil redisUtil, String apiTokenPrefix, long apiExpires) {
        super(redisUtil);
        this.apiTokenPrefix = apiTokenPrefix;
        this.apiExpires = apiExpires;
    }

    @Override
    public String createToken(Member m) {
        return createToken(m , apiExpires);
    }

    @Override
    public void updateExpires(String token){
        updateExpires(token , apiExpires);
    }

    @Override
    public String getToken(Member m){
        return apiTokenPrefix + "-" + nowStr() + CommonUtil.randomString(16);
    }
    private String nowStr(){
        return DatetimeUtils.toStr(new Date() , DatetimeUtils.SDF_DATETIME_SHORT);
    }
}

Member就代表登入實體。

2.只允許單裝置登入,即所謂的登入踢人,在登入的時候驗證是夠已經登入,如果已經登入就給出提示或者直接踢人登入。這種情形下,需要根據登入的標識確認是否已經登入,有兩種解決方式,一種是在儲存token的時候,既儲存token》》實體的關係,還需要儲存實體標識與token的關係;另外一種解決方式是token與實體標識強相關,根據實體標識即可算出token。第二種的實現為:

/**
 * 客戶端API介面token管理器【只支援單裝置登入】
 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257
 */
public class ApiTokenManager extends AbstractRedisTokenManager<Member> {
    private long apiExpires;
    public ApiTokenManager(RedisUtil redisUtil , long apiExpires){
        super(redisUtil);
        this.apiExpires = apiExpires;
    }

    @Override
    public String createToken(Member m) {
        return createToken(m , apiExpires);
    }


    @Override
    public void updateExpires(String token){
        updateExpires(token , apiExpires);
    }

    /**
     * 單裝置的token必須跟賬號強相關,即根據賬號要能算出token
     * @param m m
     */
    @Override
    public String getToken(Member m){
        return m.getPhone();
    }
}