ユーザー認証
1.1 ユーザー認証プロセスの分析
、次のようにユーザ認証プロセスは次のとおりです。
業務プロセス、次のように:
1 、クライアントは、認証サービスの認証を要求します。
2 ブラウザによって認定、認証業務クッキーが書かれている(トークンIDトークン)
認証サービスセンターは、利用者情報を照会するユーザーを要求します。
認証サービス要求春のセキュリティアプリケーショントークン。
認証サービスのトークン(IDトークン)とJWT にトークンストレージRedisの中で。
認定サービスクッキーの書き込み トークン(IDトークン)。
3 、フロントエンドは、担持トークン認証サービス取得の要求JWT トークン
遠位取得JWT トークンに格納されているのsessionStorage 。
遠位JWT 解析トークンとユーザー情報がページに表示されます。
4 、搬送フロントクッキーにおけるトークントークンと同一JWT リソースサービスにアクセスするためのトークン
フロントエンドサービス要求リソースは、2つの搬送に必要なトークンを、あるクッキーIDトークンで、aは、HTTPヘッダにJWT トークン
フロントエンドサービスリソース要求前にHTTPヘッダーを追加するJWT リソース要求
5をチェックゲートウェイ、トークンの正当性
運ぶ必要があり、ユーザーの要求をトークンIDをトークンとJWT トークン
ゲートウェイチェックはRedisのトークンが、それは、ユーザーが再度ログインする必要があり、期限が切れてい正当である 6 、リソースサービスチェックJWT 正当性をと承認の完了 リソースのサービスチェックをJWT
トークン、認証を完了するために、メソッドが正常に実行する権限を持っている、アクセス許可を拒否する方法はありません。
1.2 認証サービスは、データベース照会
1.2.1 要求分析の
口座番号とパスワードが一致を確認し、あるユーザーの身元を確認するために、データベース内のユーザ情報に基づいて認証サービスを、。
認証サービスは、データベースに直接に接続されているが、ユーザへのユーザ中心のサービスを介して中央データベースに照会しません。
1.2.2 ビルド環境
1.2.2.1 の作成、ユーザーを中心としたデータベースの
ように、ユーザ情報管理、ロール管理、著作権管理や:などのユーザー管理を担当ユーザーセンター。
作成xc_userのデータベース(MySQLの)
輸入(xc_user.sql 輸入重複せずにインポートされている)
1.2.2.2 の作成ユーザーセンタープロジェクト
のインポート「データ」 - " xc-service-ucenter.zip
1.2.4 クエリのユーザインタフェースは、
アカウントセンターによると、ユーザーを照会するユーザーを完了するために情報インタフェース機能。
1.2.4.1アピインタフェース
ユーザーは、外部インターフェイスを提供し、以下:
1 、データの種類に応じて
このインターフェースはので、ここで定義された拡張タイプ、ユーザー情報やユーザー権限情報の未来を照会するために使用されます
@Data
@ToString
public class XcUserExt extends XcUser {
//权限信息
private List<XcMenu> permissions;
//企业信息
private String companyId;
}
2 アカウントのユーザ情報の問い合わせに応じて、
@Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
public XcUserExt getUserext(String username);
}
1.2.4.2 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);
}
1.2.4.2 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;
}
}
1.2.4.3 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;
}
}
1.2.4.4 测试
使用Swagger-ui或postman测试用户信息查询接口
1.2.5 调用查询用户接口
1.2.5.1 创建client
认证服务需要远程调用用户中心服务查询用户,在认证服务中创建Feign客户端
@FeignClient(value = XcServiceList.XC_SERVICE_UCENTER)
public interface UserClient {
@GetMapping("/ucenter/getuserext")
public XcUserExt getUserext(@RequestParam("username") String username)
}
1.2.5.2 UserDetailsServiceImpl
认证服务调用spring security接口申请令牌,spring security接口会调用UserDetailsServiceImpl从数据库查询用户,如果查询不到则返回 NULL,表示不存在;在UserDetailsServiceImpl中将正确的密码返回, spring security会自动去比对输入密码的正确性。
1、修改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;
}
}
2、测试,请求http://localhost:40400/auth/userlogin
观察UserDetailsServiceImpl是否正常请求Ucenter的查询用户接口。
1.2.5.3 BCryptPasswordEncoder
早期使用md5对密码进行编码,每次算出的md5值都一样,这样非常不安全,Spring Security推荐使用
BCryptPasswordEncoder对密码加随机盐,每次的Hash值都不一样,安全性高。
1、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);
}
}
2、在AuthorizationServerConfifig配置类中配置BCryptPasswordEncoder
//采用bcrypt对密码进行Hash
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
3、测试
请求http://localhost:40400/auth/userlogin,输入正常的账号和密码进行测试
1.2.5.4 解析申请令牌错误信息
当账号输入错误应该返回用户不存在的信息,当密码错误要返回用户名或密码错误信息,业务流程图如下:
修改申请令牌的程序解析返回的错误:
由于restTemplate收到400或401的错误会抛出异常,而spring security针对账号不存在及密码错误会返回400及
401,所以在代码中控制针对400或401的响应不要抛出异常。
......
Map map = null;
try {
((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// 设置 当响应400和401时照常响应数据,不要报错
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401 ) {
super.handleError(response);
}
}
});
//http请求spring security的申请令牌接口
ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(path, HttpMethod.POST, new
HttpEntity<MultiValueMap<String, String>>(formData, header), Map.class);
map = mapResponseEntity.getBody();
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("request oauth_token_password error: {}",e.getMessage());
e.printStackTrace();
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
if(map == null ||
map.get("access_token") == null ||
map.get("refresh_token") == null ||
map.get("jti") == null){//jti是jwt令牌的唯一标识作为用户身份令牌
//获取spring security返回的错误信息
String error_description = (String) map.get("error_description");
if(StringUtils.isNotEmpty(error_description)){
if(error_description.equals("坏的凭证")){
ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
}else if(error_description.indexOf("UserDetailsService returned null")>=0){
ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
}
}
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
......
1.2.5.5 测试
使用postman请求http://localhost:40400/auth/userlogin
1、输入正确的账号和密码进行测试
从数据库找到测试账号,本课程所提供的用户信息初始密码统一为111111
2、输入错误的账号和密码进行测试