目录
文章目录
前言
一、sso结构实现
二、使用步骤
2.1 建一个spring cloud 项目
2.2 common下的core的配置
2.3 实现系统的业务微服务
2.4 sso模块的编写
总结
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是比较流行的
在总的父工程下添加 父工程使用
2.3.12.RELEASE 的springboot项目创建 然后添加以下
pom
1.8
UTF-8
UTF- 8
Hoxton.SR8
2.2.3.RELEASE
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring-cloud-alibaba.version}
pom
import
其他二级模块和三级模块都是使用maven项目 每一个maven模块最后添加一个spring boot的启动类
引入依赖
org.projectlombok lombok com.auth0 java-jwt 3.18.3 com.alibaba fastjson 1.2.83 com.baomidou mybatis-plus-boot-starter 3.5.2
添加实体类 工具类
三个实体类
@Data
@TableName(value = "cc_admin")
public class Admin implements Serializable {
/**
* 管理员表id
*/
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
/**
* 管理员id
*/
private String adminId;
/**
* 管理员用户名
*/
private String adminUsername;
/**
* 管理员密码
*/
private String adminPassword;
/**
* 管理员真实姓名
*/
private String adminRealname;
/**
* 管理员密钥
*/
private String salt;
/**
* 角色id
*/
private String roleId;
/**
* 部门id
*/
private String deptId;
/**
* 管理员性别
*/
private String adminSex;
/**
* 管理员联系方式
*/
private String adminTelephone;
/**
* 管理员年龄
*/
private Integer adminAge;
/**
* 逻辑删除0(false)未删除,1(true)已删除
*/
private Boolean isDeleted;
/**
* 创建时间
*/
private Date gmtCreate;
/**
* 修改时间
*/
private Date gmtModified;
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Admin other = (Admin) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getAdminId() == null ? other.getAdminId() == null : this.getAdminId().equals(other.getAdminId()))
&& (this.getAdminUsername() == null ? other.getAdminUsername() == null : this.getAdminUsername().equals(other.getAdminUsername()))
&& (this.getAdminPassword() == null ? other.getAdminPassword() == null : this.getAdminPassword().equals(other.getAdminPassword()))
&& (this.getAdminRealname() == null ? other.getAdminRealname() == null : this.getAdminRealname().equals(other.getAdminRealname()))
&& (this.getSalt() == null ? other.getSalt() == null : this.getSalt().equals(other.getSalt()))
&& (this.getRoleId() == null ? other.getRoleId() == null : this.getRoleId().equals(other.getRoleId()))
&& (this.getDeptId() == null ? other.getDeptId() == null : this.getDeptId().equals(other.getDeptId()))
&& (this.getAdminSex() == null ? other.getAdminSex() == null : this.getAdminSex().equals(other.getAdminSex()))
&& (this.getAdminTelephone() == null ? other.getAdminTelephone() == null : this.getAdminTelephone().equals(other.getAdminTelephone()))
&& (this.getAdminAge() == null ? other.getAdminAge() == null : this.getAdminAge().equals(other.getAdminAge()))
&& (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
&& (this.getGmtCreate() == null ? other.getGmtCreate() == null : this.getGmtCreate().equals(other.getGmtCreate()))
&& (this.getGmtModified() == null ? other.getGmtModified() == null : this.getGmtModified().equals(other.getGmtModified()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getAdminId() == null) ? 0 : getAdminId().hashCode());
result = prime * result + ((getAdminUsername() == null) ? 0 : getAdminUsername().hashCode());
result = prime * result + ((getAdminPassword() == null) ? 0 : getAdminPassword().hashCode());
result = prime * result + ((getAdminRealname() == null) ? 0 : getAdminRealname().hashCode());
result = prime * result + ((getSalt() == null) ? 0 : getSalt().hashCode());
result = prime * result + ((getRoleId() == null) ? 0 : getRoleId().hashCode());
result = prime * result + ((getDeptId() == null) ? 0 : getDeptId().hashCode());
result = prime * result + ((getAdminSex() == null) ? 0 : getAdminSex().hashCode());
result = prime * result + ((getAdminTelephone() == null) ? 0 : getAdminTelephone().hashCode());
result = prime * result + ((getAdminAge() == null) ? 0 : getAdminAge().hashCode());
result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
result = prime * result + ((getGmtCreate() == null) ? 0 : getGmtCreate().hashCode());
result = prime * result + ((getGmtModified() == null) ? 0 : getGmtModified().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", adminId=").append(adminId);
sb.append(", adminUsername=").append(adminUsername);
sb.append(", adminPassword=").append(adminPassword);
sb.append(", adminRealname=").append(adminRealname);
sb.append(", salt=").append(salt);
sb.append(", roleId=").append(roleId);
sb.append(", deptId=").append(deptId);
sb.append(", adminSex=").append(adminSex);
sb.append(", adminTelephone=").append(adminTelephone);
sb.append(", adminAge=").append(adminAge);
sb.append(", isDeleted=").append(isDeleted);
sb.append(", gmtCreate=").append(gmtCreate);
sb.append(", gmtModified=").append(gmtModified);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
/**
* 权限
* @TableName cc_permission
*/
@Data
@TableName(value = "cc_permission")
public class Permission implements Serializable {
/**
* 编号
*/
@TableId(value = "id")
private String id;
/**
* 所属上级
*/
private String pid;
/**
* 名称
*/
private String name;
/**
* 类型(1:菜单,2:按钮)
*/
private Integer type;
/**
* 权限值
*/
private String permissionValue;
/**
* 访问路径
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 图标
*/
private String icon;
/**
* 状态(0:正常,1:禁用)
*/
private Integer status;
/**
* 逻辑删除 1(true)已删除, 0(false)未删除
*/
private Integer isDeleted;
/**
* 创建时间
*/
private Date gmtCreate;
/**
* 更新时间
*/
private Date gmtModified;
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Permission other = (Permission) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getPid() == null ? other.getPid() == null : this.getPid().equals(other.getPid()))
&& (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
&& (this.getType() == null ? other.getType() == null : this.getType().equals(other.getType()))
&& (this.getPermissionValue() == null ? other.getPermissionValue() == null : this.getPermissionValue().equals(other.getPermissionValue()))
&& (this.getPath() == null ? other.getPath() == null : this.getPath().equals(other.getPath()))
&& (this.getComponent() == null ? other.getComponent() == null : this.getComponent().equals(other.getComponent()))
&& (this.getIcon() == null ? other.getIcon() == null : this.getIcon().equals(other.getIcon()))
&& (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
&& (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
&& (this.getGmtCreate() == null ? other.getGmtCreate() == null : this.getGmtCreate().equals(other.getGmtCreate()))
&& (this.getGmtModified() == null ? other.getGmtModified() == null : this.getGmtModified().equals(other.getGmtModified()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getPid() == null) ? 0 : getPid().hashCode());
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
result = prime * result + ((getPermissionValue() == null) ? 0 : getPermissionValue().hashCode());
result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode());
result = prime * result + ((getComponent() == null) ? 0 : getComponent().hashCode());
result = prime * result + ((getIcon() == null) ? 0 : getIcon().hashCode());
result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
result = prime * result + ((getGmtCreate() == null) ? 0 : getGmtCreate().hashCode());
result = prime * result + ((getGmtModified() == null) ? 0 : getGmtModified().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", pid=").append(pid);
sb.append(", name=").append(name);
sb.append(", type=").append(type);
sb.append(", permissionValue=").append(permissionValue);
sb.append(", path=").append(path);
sb.append(", component=").append(component);
sb.append(", icon=").append(icon);
sb.append(", status=").append(status);
sb.append(", isDeleted=").append(isDeleted);
sb.append(", gmtCreate=").append(gmtCreate);
sb.append(", gmtModified=").append(gmtModified);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
@Data
@TableName(value = "cc_role")
public class Role implements Serializable {
/**
* 角色表id
*/
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
/**
* 角色id
*/
private String roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色描述
*/
private String roleDescription;
/**
* 部门id
*/
private String deptId;
/**
* 角色状态0(启用)1(禁用)
*/
private Integer roleStatus;
/**
* 逻辑删除0(false)未删除,1(true)已删除
*/
private Boolean isDeleted;
/**
* 创建时间
*/
private Date gmtCreate;
/**
* 修改时间
*/
private Date gmtModified;
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Role other = (Role) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getRoleId() == null ? other.getRoleId() == null : this.getRoleId().equals(other.getRoleId()))
&& (this.getRoleName() == null ? other.getRoleName() == null : this.getRoleName().equals(other.getRoleName()))
&& (this.getRoleDescription() == null ? other.getRoleDescription() == null : this.getRoleDescription().equals(other.getRoleDescription()))
&& (this.getDeptId() == null ? other.getDeptId() == null : this.getDeptId().equals(other.getDeptId()))
&& (this.getRoleStatus() == null ? other.getRoleStatus() == null : this.getRoleStatus().equals(other.getRoleStatus()))
&& (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
&& (this.getGmtCreate() == null ? other.getGmtCreate() == null : this.getGmtCreate().equals(other.getGmtCreate()))
&& (this.getGmtModified() == null ? other.getGmtModified() == null : this.getGmtModified().equals(other.getGmtModified()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getRoleId() == null) ? 0 : getRoleId().hashCode());
result = prime * result + ((getRoleName() == null) ? 0 : getRoleName().hashCode());
result = prime * result + ((getRoleDescription() == null) ? 0 : getRoleDescription().hashCode());
result = prime * result + ((getDeptId() == null) ? 0 : getDeptId().hashCode());
result = prime * result + ((getRoleStatus() == null) ? 0 : getRoleStatus().hashCode());
result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
result = prime * result + ((getGmtCreate() == null) ? 0 : getGmtCreate().hashCode());
result = prime * result + ((getGmtModified() == null) ? 0 : getGmtModified().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", roleId=").append(roleId);
sb.append(", roleName=").append(roleName);
sb.append(", roleDescription=").append(roleDescription);
sb.append(", deptId=").append(deptId);
sb.append(", roleStatus=").append(roleStatus);
sb.append(", isDeleted=").append(isDeleted);
sb.append(", gmtCreate=").append(gmtCreate);
sb.append(", gmtModified=").append(gmtModified);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
jwt工具类和前端数据处理vo
public class JwtUtil {
private static String sign="xxx";
//1.生成jwt的token方法
public static String createJWT(Map map){
//定义头信息
Map head= new HashMap<>();
head.put("alg","HS256");
head.put("typ","JWT");
//定义颁发时间
Date iat=new Date();
//过期时间
Calendar expire = Calendar.getInstance();
expire.set(Calendar.SECOND,24*3600);//24*3600
Date expireTime = expire.getTime();
String token = JWT.create()
//头信息
.withHeader(head)
//设置颁发时间
.withIssuedAt(iat)
//设置过期时间
.withExpiresAt(expireTime)
//自定义的内容
.withClaim("userinfo",map)
//签名
.sign(Algorithm.HMAC256(sign));
return token;
}
//2.校验token是否有效
public static boolean verifyToken(String token){
//获取一个JWT校验对象
JWTVerifier build = JWT.require(Algorithm.HMAC256(sign)).build();
try {
//调用校验功能
DecodedJWT verify = build.verify(token);
return true;
}catch (Exception e){
System.out.println("token无效");
return false;
}
}
//3.从token中获取相关的载荷内容
public static Map getTokenChaim(String token){
//获取一个JWT校验对象
JWTVerifier build = JWT.require(Algorithm.HMAC256(sign)).build();
//调用校验功能
DecodedJWT verify = build.verify(token);
Claim loginInfo = verify.getClaim("userinfo");
return loginInfo.asMap();
}
}
vo 返回给前端的统一数据处理类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
在modules添加需要的依赖
com.xin
xin-common-core
0.0.1-SNAPSHOT
mysql
mysql-connector-java
5.1.49
<-- web启动类 -->
org.springframework.boot
spring-boot-starter-web
<-- nacos注册中心 -->
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
<-- 微服务之间的调用 -->
org.springframework.cloud
spring-cloud-starter-openfeign
在modules下新建system子模块 dao层和service使用mybatisX插件生成 启动类使用spring boot的
主要展示服务之间的控制层
@RestController
@RequestMapping("system/permission")
public class PermissionController {
@Autowired
private PermissionService permissionService;
@GetMapping("getPermissionByUserid/{userid}")
public List getPermissionByUserid(@PathVariable Integer userid){
return permissionService.selectPermissionByUserid(userid);
}
}
@RestController
@RequestMapping("system/admin")
public class AdminController {
@Autowired
private AdminService adminService;
@GetMapping("/getByName/{name}")
public Admin getByName(@PathVariable String name){
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("admin_username",name);
Admin admin = adminService.getOne(wrapper);
return admin;
}
}
配置文件
server.port=8087
spring.application.name=cai-system
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/caicai?serverTimezone=Asia/Shanghai
spring.datasource.password=root
spring.datasource.username=root
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
spring.cloud.nacos.discovery.server-addr=localhost:8848
添加依赖
<-- rdis启动 --> com.xin xin-common-core 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-security org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-sentinel
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate redisTemplate;
@Bean
public PasswordEncoder passwordEncoder() {
//加盐加密: 原密码+盐===>哈希加密===>密文
// 原密码===>哈希加密===>密文 [存储大量常见的密文]
//123456+随机产生盐===>密文
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(successHandler())
.failureHandler(failureHandler())
.loginProcessingUrl("/login").permitAll();
http.cors();
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
private AuthenticationSuccessHandler successHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
//获取账号和权限
String username = authentication.getPrincipal().toString();
Collection authorities = authentication.getAuthorities().stream().map(item->item.getAuthority()).collect(Collectors.toList());
//根据账号和权限生产token;
Map map=new HashMap<>();
map.put("username",username);
map.put("authorities",authorities);
String token = JwtUtil.createJWT(map);
//存入redis中。
redisTemplate.opsForValue().set(token,"",30, TimeUnit.MINUTES);
Result result=new Result<>(2000,"登录成功",token);
String jsonString = JSON.toJSONString(result);
writer.print(jsonString);
writer.flush();
writer.close();
};
}
private AuthenticationFailureHandler failureHandler() {
return (request, response, e) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result=new Result<>(5000,"登录失败");
String jsonString = JSON.toJSONString(result);
writer.print(jsonString);
writer.flush();
writer.close();
};
}
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("123456"));
}
}
两个微服务的调用
@FeignClient(value = "cai-system")
public interface AdminFeign {
//建议:如果是服务之间的调用直接返回对象,如果是前端调用CommonResult
@GetMapping("/system/admin/getByName/{name}")
public Admin getByName(@PathVariable String name);
}
@FeignClient(value = "cai-system")
public interface PermissionFeign {
@GetMapping("/system/permission/getPermissionByUserid/{userid}")
public List getPermissionByUserid(@PathVariable Integer userid);
}
自定义登录规则
@Service
public class UserService implements UserDetailsService {
@Autowired
private AdminFeign adminFeign;
@Autowired
private PermissionFeign permissionFeign;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.根据用户名查询用户信息
Admin admin = adminFeign.getByName(username);
if(admin!=null){
//2.根据当前用户查询该用户具有的权限。
List permissions = permissionFeign.getPermissionByUserid(admin.getId());
Collection authorities = permissions.stream().filter(item->item.getPermissionValue()!=null).filter(item->item.getType()==2).map(item->new SimpleGrantedAuthority(item.getPermissionValue())).collect(Collectors.toList());
User user = new User(admin.getAdminUsername(),admin.getAdminPassword(),authorities);
return user;
}
return null;
}
}
主启动类
/**
* 排除多余的自动装配类可开启openfeign
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableFeignClients
public class SsoApp {
public static void main(String[] args) {
SpringApplication.run(SsoApp.class,args);
}
}
配置文件application.properties
server.port=8088
spring.application.name=cai-sso
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.redis.host=localhost
spring.redis.port=6379
#解决多个FeignClientSpecification' could not be registered
#开启重写多个feign接口
spring.main.allow-bean-definition-overriding=true
这样SSo模块就完成了
待补充