學成在線(第17天)

 用戶認證

 用戶認證流程分析

用戶認證流程以下:前端

 業務流程說明以下:web

一、客戶端請求認證服務進行認證。
二、認證服務認證經過向瀏覽器cookie寫入token(身份令牌)
認證服務請求用戶中心查詢用戶信息。
認證服務請求Spring Security申請令牌。
認證服務將token(身份令牌)和jwt令牌存儲至redis中。
認證服務向cookie寫入 token(身份令牌)。
三、前端攜帶token請求認證服務獲取jwt令牌
前端獲取到jwt令牌並存儲在sessionStorage。
前端從jwt令牌中解析中用戶信息並顯示在頁面。
四、前端攜帶cookie中的token身份令牌及jwt令牌訪問資源服務
前端請求資源服務須要攜帶兩個token,一個是cookie中的身份令牌,一個是http header中的jwt令牌
前端請求資源服務前在http header上添加jwt請求資源
五、網關校驗token的合法性redis

用戶請求必須攜帶 token身份令牌和jwt令牌
網關校驗redis中token是否合法,已過時則要求用戶從新登陸
六、資源服務校驗jwt的合法性並完成受權
資源服務校驗jwt令牌,完成受權,擁有權限的方法正常執行,沒有權限的方法將拒絕訪問。spring

查詢用戶接口

Api接口

用戶中心對外提供以下接口:
一、響應數據類型數據庫

此接口未來被用來查詢用戶信息及用戶權限信息,因此這裏定義擴展類型json

@Data
@ToString
public class XcUserExt extends XcUser {
    //權限信息
    private List<XcMenu> permissions;
    //企業信息
    private String companyId;
}

二、根據帳號查詢用戶信息api

@Api(value = "用戶中心",description = "用戶中心管理")
public interface UcenterControllerApi {
    public XcUserExt getUserext(String username);
}

DAO

添加XcUser、XcCompantUser兩個表的Dao瀏覽器

public interface XcUserRepository extends JpaRepository<XcUser, String> {
        XcUser findXcUserByUsername(String username);
}
public interface XcCompanyUserRepository extends JpaRepository<XcCompanyUser,String> {
    //根據用戶id查詢所屬企業id
    XcCompanyUser findByUserId(String userId);
}

Service

@Service
public class UserService {
    @Autowired
    private XcUserRepository xcUserRepository;
    //根據用戶帳號查詢用戶信息
    public XcUser findXcUserByUsername(String username){
        return xcUserRepository.findXcUserByUsername(username);
    }
    //根據帳號查詢用戶的信息,返回用戶擴展信息
    public XcUserExt getUserExt(String username){
        XcUser xcUser this.findXcUserByUsername(username);
        if(xcUser == null){
            return null;
}
        XcUserExt xcUserExt new XcUserExt();
        BeanUtils.copyProperties(xcUser,xcUserExt);
        //用戶id
        String userId = xcUserExt.getId();
        //查詢用戶所屬公司
        XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findXcCompanyUserByUserId(userId);
        if(xcCompanyUser!=null){
            String companyId = xcCompanyUser.getCompanyId();
            xcUserExt.setCompanyId(companyId);
        }
        return xcUserExt;
    }
}
View Code

Controller

@RestController
@RequestMapping("/ucenter")
public class UcenterController implements UcenterControllerApi {
    @Autowired
    UserService userService;
    @Override
    @GetMapping("/getuserext")
    public XcUserExt getUserext(@RequestParam("username") String username) {
        XcUserExt xcUser = userService.getUserExt(username);
        return xcUser;
    }
  }

 調用查詢用戶接口

 建立client

認證服務須要遠程調用用戶中心服務查詢用戶,在認證服務中建立Feign客戶端安全

@FeignClient(value = XcServiceList.XC_SERVICE_UCENTER)
public interface UserClient {
@GetMapping("/ucenter/getuserext")    
    public XcUserExt getUserext(@RequestParam("username") String username)
}

UserDetailsServiceImpl

認證服務調用spring security接口申請令牌,spring security接口會調用UserDetailsServiceImpl從數據庫查詢用
戶,若是查詢不到則返回 NULL,表示不存在;在UserDetailsServiceImpl中將正確的密碼返回, spring security
會自動去比對輸入密碼的正確性。cookie

一、修改UserDetailsServiceImpl的loadUserByUsername方法,調用Ucenter服務的查詢用戶接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserClient userClient;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //取出身份,若是身份爲空說明沒有認證
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //沒有認證統一採用httpbasic認證,httpbasic中存儲了client_id和client_secret,開始認證
client_id和client_secret
        if(authentication==null){
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
            if(clientDetails!=null){
                //密碼
                String clientSecret = clientDetails.getClientSecret();
                return new
User(username,clientSecret,AuthorityUtils.commaSeparatedStringToAuthorityList(""));
            }
        }
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        //請求ucenter查詢用戶
        XcUserExt userext = userClient.getUserext(username);
        if(userext == null){
            //返回NULL表示用戶不存在,Spring Security會拋出異常
            return null;
        }
        //從數據庫查詢用戶正確的密碼,Spring Security會去比對輸入密碼的正確性
        String password = userext.getPassword();
        String user_permission_string = "";
        UserJwt userDetails new UserJwt(username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
        //用戶id
        userDetails.setId(userext.getId());
        //用戶名稱
        userDetails.setName(userext.getName());
        //用戶頭像
        userDetails.setUserpic(userext.getUserpic());
        //用戶所屬企業id
        userDetails.setCompanyId(userext.getCompanyId());
        return userDetails;
    }
}
View Code

BCryptPasswordEncoder

早期使用md5對密碼進行編碼,每次算出的md5值都同樣,這樣很是不安全,Spring Security推薦使用
BCryptPasswordEncoder對密碼加隨機鹽,每次的Hash值都不同,安全性高。
一、BCryptPasswordEncoder測試程序以下

@Test
public void testPasswrodEncoder(){
    String password = "111111";
    PasswordEncoder passwordEncoder new BCryptPasswordEncoder();
    for(int i=0;i<10;i++) {
        //每一個計算出的Hash值都不同
        String hashPass = passwordEncoder.encode(password);
        System.out.println(hashPass);
        //雖然每次計算的密碼Hash值不同可是校驗是經過的
        boolean f = passwordEncoder.matches(password, hashPass);
        System.out.println(f);
    }
}

二、在AuthorizationServerConfig配置類中配置BCryptPasswordEncoder

//採用bcrypt對密碼進行Hash
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

 前端顯示當前用戶

 需求分析

用戶登陸成功在頁頭顯示當前登陸的用戶名稱。

數據流程以下圖:

一、用戶請求認證服務,登陸成功。
二、用戶登陸成功,認證服務向cookie寫入身份令牌,向redis寫入user_token(身份令牌及受權jwt受權令牌)
三、客戶端攜帶cookie中的身份令牌請求認證服務獲取jwt令牌。
四、客戶端解析jwt令牌,並將解析的用戶信息存儲到sessionStorage中。
jwt令牌中包括了用戶的基本信息,客戶端解析jwt令牌便可獲取用戶信息。
五、客戶端從sessionStorage中讀取用戶信息,並在頁頭顯示。

jwt 查詢接口

 需求分析

認證服務對外提供jwt查詢接口,流程以下:

1 、客戶端攜帶cookie中的身份令牌請求認證服務獲取jwt
二、認證服務根據身份令牌從redis中查詢jwt令牌並返回給客戶端。

API

在認證模塊定義 jwt查詢接口:

@Api(value = "jwt查詢接口",description = "客戶端查詢jwt令牌內容")
public interface AuthControllerApi {
    @ApiOperation("查詢userjwt令牌")
    public JwtResult userjwt();

Service

在AuthService中定義方法以下:

//從redis查詢令牌
    public AuthToken getUserToken(String token){
        String userToken = "user_token:"+token;
        String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
        if(userToken!=null){
            AuthToken authToken null;
            try {
                authToken = JSON.parseObject(userTokenString, AuthToken.class);
            } catch (Exception e) {
                LOGGER.error("getUserToken from redis and execute JSON.parseObject error
{}",e.getMessage());
                e.printStackTrace();
            }
            return authToken;
        }
        return null;
    }

Controller

 @Override
    @GetMapping("/userjwt")
    public JwtResult userjwt() {
        //獲取cookie中的令牌
String access_token = getTokenFormCookie();
        //根據令牌從redis查詢jwt
        AuthToken authToken = authService.getUserToken(access_token);
        if(authToken == null){
            return new JwtResult(CommonCode.FAIL,null);
        }
        return new JwtResult(CommonCode.SUCCESS,authToken.getJwt_token());
    }
    //從cookie中讀取訪問令牌
    private String getTokenFormCookie(){
        Map<String, String> cookieMap = CookieUtil.readCookie(request, "uid");
        String access_token = cookieMap.get("uid");
        return access_token;
    }

測試

使用postman測試
一、請求 /auth/userlogin

 觀察cookie是否已存入用戶身份令牌。

二、get請求jwt

  用戶退出

 需求分析

操做流程以下:
一、用戶點擊退出,彈出退出確認窗口,點擊肯定

 二、退出成功

用戶退出要如下動做:
一、刪除redis中的token
二、刪除cookie中的token

API

認證服務對外提供退出接口。

@ApiOperation("退出")
public ResponseResult logout();

 服務端

認證服務提供退出接口。

Service

//從redis中刪除令牌
public boolean delToken(String access_token){
    String name = "user_token:" + access_token;
    stringRedisTemplate.delete(name);
    return true;
}

Controller

//退出
    @Override
    @PostMapping("/userlogout")
    public ResponseResult logout() {
        //取出身份令牌
        String uid = getTokenFormCookie();
        //刪除redis中token
        authService.delToken(uid);
        //清除cookie
        clearCookie(uid);
        return new ResponseResult(CommonCode.SUCCESS);
    }
    //清除cookie
    private void clearCookie(String token){
        CookieUtil.addCookie(response, cookieDomain, "/", "uid", token, 0, false);
    }

退出URL放行

認證服務默認都要校驗用戶的身份信息,這裏須要將退出url放行。
在WebSecurityConfig類中重寫 configure(WebSecurity web)方法,以下:

@Override    
    public void configure(WebSecurity web) throws Exception {
         web.ignoring().antMatchers("/userlogin","/userlogout");
    }

Zuul 網關

Zuul 介紹

什麼是Zuul?
Spring Cloud Zuul是整合Netflix公司的Zuul開源項目實現的微服務網關,它實現了請求路由、負載均衡、校驗過
慮等 功能。

什麼是網關?
服務網關是在微服務前邊設置一道屏障,請求先到服務網關,網關會對請求進行過慮、校驗、路由等處理。有了服
務網關能夠提升微服務的安全性,網關校驗請求的合法性,請求不合法將被攔截,拒絕訪問。
Zuul與Nginx怎麼配合使用?
Zuul與Nginx在實際項目中須要配合使用,以下圖,Nginx的做用是反向代理、負載均衡,Zuul的做用是保障微服
務的安全訪問,攔截微服務請求,校驗合法性及負載均衡。

 路由配置

需求分析

Zuul網關具備代理的功能,根據請求的url轉發到微服務,以下圖:

客戶端請求網關/api/learning,經過路由轉發到/learning
客戶端請求網關/api/course,經過路由轉發到/course

路由配置

在appcation.yml中配置:

zuul:
  routes:
    manage‐course:  #路由名稱,名稱任意,保持全部路由名稱惟一
      path: /course/** 
      serviceId: xc‐service‐manage‐course #指定服務id,從Eureka中找到服務的ip和端口
      #url: http://localhost:31200 #也可指定url
      strip‐prefix: false #true:代理轉發時去掉前綴,false:代理轉發時不去掉前綴
      sensitiveHeaders: #默認zuul會屏蔽cookie,cookie不會傳到下游服務,這裏設置爲空則取消默認的黑名
單,若是設置了具體的頭信息則不會傳到下游服務
#   ignoredHeaders: Authorization 

serviceId:推薦使用serviceId,zuul會從Eureka中找到服務id對應的ip和端口。
strip-prefix: false #true:代理轉發時去掉前綴,false:代理轉發時不去掉前綴,例如,爲true請
求/course/coursebase/get/..,代理轉發到/coursebase/get/,若是爲false則代理轉發到/course/coursebase/get

sensitiveHeaders :敏感頭設置,默認會過慮掉cookie,這裏設置爲空表示不過慮
ignoredHeaders:能夠設置過慮的頭信息,默認爲空表示不過慮任何頭

 完整的路由配置

zuul:
  routes:
    xc‐service‐learning:  #路由名稱,名稱任意,保持全部路由名稱惟一
      path: /learning/**
      serviceId: xc‐service‐learning #指定服務id,從Eureka中找到服務的ip和端口
      strip‐prefix: false
      sensitiveHeaders:
    manage‐course:
      path: /course/**
      serviceId: xc‐service‐manage‐course
      strip‐prefix: false
      sensitiveHeaders:
    manage‐cms:
      path: /cms/**
      serviceId: xc‐service‐manage‐cms
      strip‐prefix: false
      sensitiveHeaders:
    manage‐sys:
      path: /sys/**
      serviceId: xc‐service‐manage‐cms
      strip‐prefix: false
sensitiveHeaders:
    service‐ucenter:
      path: /ucenter/**
      serviceId: xc‐service‐ucenter
      sensitiveHeaders:
      strip‐prefix: false
    xc‐service‐manage‐order:
       path: /order/**
       serviceId: xc‐service‐manage‐order
       sensitiveHeaders:
       strip‐prefix: false

過濾器

Zuul的核心就是過慮器,經過過慮器實現請求過慮,身份校驗等。

ZuulFilter

自定義過慮器須要繼承 ZuulFilter,ZuulFilter是一個抽象類,須要覆蓋它的四個方法,以下:
一、 shouldFilter:返回一個Boolean值,判斷該過濾器是否須要執行。返回true表示要執行此過慮器,不然不執
行。 二、 run:過濾器的業務邏輯。 三、 filterType:返回字符串表明過濾器的類型,以下 pre:請求在被路由以前
執行 routing:在路由請求時調用 post:在routing和errror過濾器以後調用 error:處理請求時發生錯誤調用
四、 filterOrder:此方法返回整型數值,經過此數值來定義過濾器的執行順序,數字越小優先級越高。

測試

過慮全部請求,判斷頭部信息是否有Authorization,若是沒有則拒絕訪問,不然轉發到微服務。
定義過慮器,使用@Component標識爲bean。

@Component
public class LoginFilterTest extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(LoginFilterTest.class);
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 2;//int值來定義過濾器的執行順序,數值越小優先級越高
    }
    @Override
    public boolean shouldFilter() {// 該過濾器須要執行
        return true;
 }
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();
        HttpServletRequest request = requestContext.getRequest();
        //取出頭部信息Authorization
        String authorization = request.getHeader("Authorization");
        if(StringUtils.isEmpty(authorization)){
            requestContext.setSendZuulResponse(false);// 拒絕訪問
         requestContext.setResponseStatusCode(200);// 設置響應狀態碼    
         ResponseResult unauthenticated = new ResponseResult(CommonCode.UNAUTHENTICATED);    
         String jsonString = JSON.toJSONString(unauthenticated);    
         requestContext.setResponseBody(jsonString);    
         requestContext.getResponse().setContentType("application/json;charset=UTF‐8");    
            return null;
        }
        return null;
    }
}
View Code

測試:
請求:http://localhost:50201/api/course/coursebase/get/4028e581617f945f01617f9dabc40000查詢課程信息

相關文章
相關標籤/搜索