A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

【郑州校区】学成在线-第17天-讲义-用户认证 Zuul 一

1 用户认证
1.1 用户认证流程分析
用户认证流程如下:

业务流程说明如下:
1、客户端请求认证服务进行认证。
2、认证服务认证通过向浏览器cookie写入token(身份令牌)
认证服务请求用户中心查询用户信息。
认证服务请求Spring Security申请令牌。
认证服务将token(身份令牌)jwt令牌存储至redis中。
认证服务向cookie写入 token(身份令牌)
3、前端携带token请求认证服务获取jwt令牌
前端获取到jwt令牌并存储在sessionStorage
前端从jwt令牌中解析中用户信息并显示在页面。
4、前端携带cookie中的token身份令牌及jwt令牌访问资源服务
前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt令牌
前端请求资源服务前在http header上添加jwt请求资源
5、网关校验token的合法性

用户请求必须携带token身份令牌和jwt令牌
网关校验redistoken是否合法,已过期则要求用户重新登录
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 Api接口
用户中心对外提供如下接口:
1、响应数据类型

此接口将来被用来查询用户信息及用户权限信息,所以这里定义扩展类型
[AppleScript] 纯文本查看 复制代码
 @Data
@ToString
public class XcUserExt extends XcUser {
//权限信息
private List<XcMenu> permissions;
//企业信息
private String companyId;
}


2、根据账号查询用户信息
[AppleScript] 纯文本查看 复制代码
 @Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
public XcUserExt getUserext(String username);
}


1.2.4.2 DAO
添加XcUserXcCompantUser两个表的Dao
[AppleScript] 纯文本查看 复制代码
 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
[AppleScript] 纯文本查看 复制代码
 @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
[AppleScript] 纯文本查看 复制代码
@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-uipostman测试用户信息查询接口
1.2.5 调用查询用户接口
1.2.5.1 创建client
认证服务需要远程调用用户中心服务查询用户,在认证服务中创建Feign客户端
[AppleScript] 纯文本查看 复制代码
 @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、修改UserDetailsServiceImplloadUserByUsername方法,调用Ucenter服务的查询用户接口

[AppleScript] 纯文本查看 复制代码
@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值都不一样,安全性高。
1BCryptPasswordEncoder测试程序如下
[AppleScript] 纯文本查看 复制代码
 @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
[AppleScript] 纯文本查看 复制代码
 //采用bcrypt对密码进行Hash
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}


3、测试
请求http://localhost:40400/auth/userlogin,输入正常的账号和密码进行测试
1.2.5.4 解析申请令牌错误信息
当账号输入错误应该返回用户不存在的信息,当密码错误要返回用户名或密码错误信息,业务流程图如下:


修改申请令牌的程序解析返回的错误:
由于restTemplate收到400401的错误会抛出异常,而spring security针对账号不存在及密码错误会返回400
401,所以在代码中控制针对400401的响应不要抛出异常。
[AppleScript] 纯文本查看 复制代码
 ......
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、输入错误的账号和密码进行测试


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马