05-单点登陆系统(SSO)设计及实现

return (httpServletRequest, httpServletResponse, e)->{

Map<String,Object> map=new HashMap<>();

map.put(“state”,401);//SC_UNAUTHORIZED 的值为401

map.put(“message”,“请先登录再访问”);

WebUtils.writeJsonToClient(httpServletResponse,map);

};

}

}

认证逻辑对象


定义业务对象,处理客户端的登陆请求,例如:

package sso.auth.service;

@Service

public class UserDetailServiceImpl implements UserDetailsService {

@Autowired

private BCryptPasswordEncoder passwordEncoder;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//1.基于用户名从数据库查询用户信息

//SysUser user=userMapper.selectUserByUsername(username);//查数据库

if(!“jack”.equals(username))//假设这是从数据库查询的信息

throw new UsernameNotFoundException(“user not exists”);

//2.将用户信息封装到UserDetails对象中并返回

//假设这个密码是从数据库查询出来的

String encodedPwd=passwordEncoder.encode(“123456”);

//假设这个权限信息也是从数据库查询到的

//List permissions=userMapper.selectUserPermissions(username);//查数据库

//假如分配权限的方式是角色,编写字符串时用"ROLE_"做前缀

List grantedAuthorities =

AuthorityUtils.commaSeparatedStringToAuthorityList( “sys:res:retrieve,sys:res:create”);

//这个user是SpringSecurity提供的UserDetails接口的实现,用于封装用户信息

//后续我们也可以基于需要自己构建UserDetails接口的实现

User user=new User(username,encodedPwd,grantedAuthorities);

return user;//这里的返回值会交给springsecurity去校验

}

}

此对象编写好以后,可以启动服务基于postman进行登陆访问测试。

资源服务实现

================================================================

工具类


第一步:定义JWT工具类,主要用于解析JWT令牌,例如

package sso.resource.util;

public class JwtUtils {

private static String secret=“AAABBBCCCDDDEEE”;

/*基于负载和算法创建token信息/

public static String generatorToken(Map<String,Object> map){

return Jwts.builder()

.setClaims(map)

.setExpiration(new Date(System.currentTimeMillis()+30601000))

.setIssuedAt(new Date())

.signWith(SignatureAlgorithm.HS256,secret)

.compact();//签约,创建token

}

/*解析token获取数据/

public static Claims getClaimsFromToken(String token){

return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

}

/*判定token是否失效/

public static boolean isTokenExpired(String token){

Date expiration=getClaimsFromToken(token).getExpiration();

return expiration.before(new Date());

}

}

第二步:定义Web工具类,用于向客户端响应json数据

package sso.auth.util;

public class WebUtils {

public static void writeJsonToClient(HttpServletResponse response, Map<String,Object> map)

throws IOException {

//1设置响应数据的编码

response.setCharacterEncoding(“utf-8”);

//2告诉浏览器响应数据的内容类型以及编码

response.setContentType(“application/json;charset=utf-8”);

//3获取输出流对象

PrintWriter out=response.getWriter();

//4 将map转换为json数据

String result=new ObjectMapper().writeValueAsString(map);

//5 将数据响应到客户端

out.println(result);

out.flush();

}

}

安全配置类


在资源服务中定义权限配置类,默认将所有认证请求放行,例如:

package sso.resource.config;

@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http.csrf().disable();

http.exceptionHandling()

.accessDeniedHandler(accessDeniedHandler());

http.authorizeRequests().anyRequest().permitAll();

}

//没有权限时执行此处理器方法

public AccessDeniedHandler accessDeniedHandler(){

return (httpServletRequest, httpServletResponse, e)-> {

Map<String,Object> map=new HashMap<>();

map.put(“state”,403);//SC_FORBIDDEN的值是403

map.put(“message”,“没有访问权限,请联系管理员”);

WebUtils.writeJsonToClient(httpServletResponse,map);

};

}

}

资源服务对象


定义资源服务对象,用于处理客户端的资源访问服务,例如:

package sso.resource.controller;

@RestController

public class ResourceController {

@PreAuthorize(“hasAuthority(‘sys:res:create’)”)

@RequestMapping(“/doCreate”)

public String doCreate(HttpServletResponse response){

return “create resource (insert data) ok”;

}

/*查询操作/

@PreAuthorize(“hasAuthority(‘sys:res:retrieve’)”)

@RequestMapping(“/doRetrieve”)

public String doRetrieve(){//Retrieve 表示查询

return “query resource (select data) ok”;

}

/*修改操作/

@PreAuthorize(“hasAuthority(‘sys:res:update’)”)

@RequestMapping(“/doUpdate”)

public String doUpdate(){

return “update resource (update data) ok”;

}

/*删除操作/

@PreAuthorize(“hasAuthority(‘sys:res:delete’)”)

@RequestMapping(“/doDelete”)

public String doDelete(){

return “delete resource (dalete data) ok”;

}

}

Spring MVC拦截器


资源服务器中的资源不是所有人都可以访问的,需要具备一定权限才可以,首先我们要判定是否登陆,然后判定登陆用户是否有权限,有访问权限才可以授权访问,这个操作可以放到spring mvc拦截器中进行实现,例如:

第一步:定义Spring MVC 拦截器.

package sso.resource.interceptor;

public class TokenInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

String token=request.getHeader(“token”);

if(token==null||“”.equals(token)) throw new RuntimeException(“请先登陆”);

if(JwtUtils.isTokenExpired(token)) throw new RuntimeException(“请先登陆”);

Claims claims=JwtUtils.getClaimsFromToken(token);

List list = (List) claims.get(“authorities”);

String[]authorities=list.toArray(new String[]{});

UserDetails userDetails= User.builder()

.username((String)claims.get(“username”))

.password(“”)

.authorities(authorities)

.build();

//将UserDetails对象保存到一个可以和Spring-Security交互的对象中

PreAuthenticatedAuthenticationToken authenticationToken=

new PreAuthenticatedAuthenticationToken(

userDetails,userDetails.getPassword(),

AuthorityUtils.createAuthorityList(authorities));

//将本次解析的用户详情和当前请求关联

//关联之后才能在后面的控制器中获得用户详情

authenticationToken.setDetails(new WebAuthenticationDetails(request));

//将当前用户详情保存到Spring-Security上下文

SecurityContextHolder.getContext().setAuthentication(authenticationToken);

return true;

}

}

第二步:创建Spring Web配置类,用于注册和配置Spring MVC拦截器,例如:

package sso.resource.config;

@Configuration

public class WebConfig implements WebMvcConfigurer {

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new TokenInterceptor())

.addPathPatterns(“/**”);

}

}

资源访问测试


第一步:启动认证服务器,通过postman进行登陆认证,例如:

在这里插入图片描述

第二步:启动资源服务器,并基于认证服务器返回的令牌进行资源访问

在这里插入图片描述

创建通用工程

================================================================

背景分析


当多个项目都有一部分公共资源需要重复编写时,我们可以创建一个公共工程,在这个工程中创建共性对象和依赖.其它工程需要时直接引用即可.

创建工程


![在这里插入图片描述]( )初始化工程


第一步:添加项目依赖

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-security

io.jsonwebtoken

jjwt

0.9.1

第二步:拷贝工具类

将sso-auth工程中的WebUtils,JwtUtils拷贝到sso-common工程的sso.util包中

创建跨域配置类


在前后端分离工程中,当通过前端工程访问认证服务和资源服务时,需要进行跨域配置,例如:

package sso.config;

@Configuration

public class CorsFilterConfig {

/*服务端过滤器层面的跨域设计/

@Bean

public FilterRegistrationBean filterFilterRegistrationBean(){

//1.对此过滤器进行配置(跨域设置-url,method)

UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();

CorsConfiguration config=new CorsConfiguration();

config.addAllowedHeader(“*”);//所有请求头信息

config.addAllowedMethod(“*”);//所有请求方式,post,delete,get,put,…

config.addAllowedOrigin(“*”);//所有请求参数

config.setAllowCredentials(true);//所有认证信息,例如cookie

//2.注册过滤器并设置其优先级

configSource.registerCorsConfiguration(“/**”, config);

FilterRegistrationBean fBean=

new FilterRegistrationBean(

new CorsFilter(configSource));

//设置此过滤器的优先级最高

fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

return fBean;

}

}

引用通用工程


第一步:删除sso-auth,sso-resource 工程下的util包

第二步:删除sso-auth,sso-resource 工程中的公共依赖

第三步:在sso-auth,sso-resource工程中添加通用工程依赖

com.cy.jt

sso-common

1.0-SNAPSHOT

启动工程测试


打开postman进行重新登录,访问资源测试.

创建前端工程

================================================================

背景分析


我们做后端,一般在测试时直接基于postman进行访问就可以,为了更好理解前后端通讯过程,我们暂且基于springboot工程构建一个前端工程.

创建前端工程


在这里插入图片描述

初始化工程


第一步:添加web依赖,代码如下:

org.springframework.boot

spring-boot-starter-web

第二步:创建application.yml配置文件,代码如下:

server:

port: 80

第三步:创建启动类,代码如下:

package sso;

@SpringBootApplication

public class UIApplication {

public static void main(String[] args) {

SpringApplication.run(UIApplication.class,args);

}

}

创建静态页面


将课前资料中的static目录直接拷贝到项目中的resource目录下.

登录页面login.html内容如下:

<!doctype html>

login

Please Login

<button type=“button” @click=“doLogin()” class=“btn btn-primary”>Submit

登录成功页面index.html,代码如下:

Title

Index Page

CRUD(Create,Retrieve,Update,Delete) Operation

    • Retrieve(查询-select)
    • Delete(删除-delete)
    • 工程访问测试


      启动sso-auth,sso-resource,sso-ui 工程,然后访问http://localhost/login.html进行登录

      在这里插入图片描述

      输入正确的账号,密码执行登录,登录成功以后跳转到如下页面.

      在这里插入图片描述

      然后,对create,update选项进行访问,检测输出结果.

      资源访问过程分析


      从登录认证,到资源访问,其过程如下:

      在这里插入图片描述

      数据库访问操作

      =================================================================

      背景分析


      目前我们登录时的账号,用户权限信息都是写死在UserServiceImpl类中的,实际项目中会从数据库查询用户以及用户对应权限信息.

      业务及表设计


      实际项目中用户权限控制,通常是通过用户,角色,菜单以及他们的关系表进行数据存储,其业务描述如下:

      在这里插入图片描述

      其sql脚本如下:

      DROP DATABASE IF EXISTS jt_security;

      CREATE DATABASE jt_security DEFAULT CHARACTER SET utf8mb4;

      SET NAMES utf8mb4;

      SET FOREIGN_KEY_CHECKS = 0;

      use jt_security;

      CREATE TABLE sys_menu (

      id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,

      name varchar(50) NOT NULL COMMENT ‘权限名称’,

      permission varchar(200) DEFAULT NULL COMMENT ‘权限标识’,

      PRIMARY KEY (id) USING BTREE

      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘权限表’;

      CREATE TABLE sys_role (

      id bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘角色ID’,

      role_name varchar(50) NOT NULL COMMENT ‘角色名称’,

      PRIMARY KEY (id) USING BTREE

      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色表’;

      CREATE TABLE sys_role_menu (

      id bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,

      role_id bigint(11) DEFAULT NULL COMMENT ‘角色ID’,

      menu_id bigint(11) DEFAULT NULL COMMENT ‘权限ID’,

      PRIMARY KEY (id) USING BTREE

      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色与权限关系表’;

      CREATE TABLE sys_user (

      id bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘用户ID’,

      username varchar(50) NOT NULL COMMENT ‘用户名’,

      password varchar(100) DEFAULT NULL COMMENT ‘密码’,

      status varchar(10) DEFAULT NULL COMMENT ‘状态 PROHIBIT:禁用 NORMAL:正常’,

      PRIMARY KEY (id) USING BTREE,

      UNIQUE KEY username (username) USING BTREE

      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘系统用户表’;

      CREATE TABLE sys_user_role (

      id bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,

      user_id bigint(11) DEFAULT NULL COMMENT ‘用户ID’,

      role_id bigint(11) DEFAULT NULL COMMENT ‘角色ID’,

      PRIMARY KEY (id) USING BTREE

      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘用户与角色关系表’;

      INSERT INTO sys_menu VALUES (1, ‘select users’, ‘sys:res:create’);

      INSERT INTO sys_menu VALUES (2, ‘select menus’, ‘sys:res:retrieve’);

      INSERT INTO sys_menu VALUES (3, ‘select roles’, ‘sys:res:delete’);

      INSERT INTO sys_role VALUES (1, ‘ADMIN’);

      INSERT INTO sys_role VALUES (2, ‘USER’);

      INSERT INTO sys_role_menu VALUES (1, 1, 1);

      INSERT INTO sys_role_menu VALUES (2, 1, 2);

      INSERT INTO sys_role_menu VALUES (3, 1, 3);

      INSERT INTO sys_role_menu VALUES (4, 2, 1);

      INSERT INTO sys_user VALUES (1,‘admin’,‘$2a 10 10 10hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’,‘NORMAL’);

      INSERT INTO sys_user VALUES (2,‘user’,‘$2a 10 10 10hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’,‘NORMAL’);

      INSERT INTO sys_user_role VALUES (1, 1, 1);

      INSERT INTO sys_user_role VALUES (2, 2, 2);

      项目初始化


      第一步:在sso-auth工程中添加,如下依赖:

      org.mybatis.spring.boot

      mybatis-spring-boot-starter

      2.2.0

      mysql

      mysql-connector-java

      8.0.21

      org.springframework.boot

      spring-boot-starter-test

      test

      第二步:修改sso-auth工程中配置文件,添加访问数据库部分,例如:

      spring:

      datasource:

      url: jdbc:mysql:///jt_security?serverTimezone=Asia/Shanghai&characterEncoding=utf8

      username: root

      password: root

      driver-class-name: com.mysql.cj.jdbc.Driver #可以省略,默认会自动识别

      业务代码编写


      第一步:创建UserMapper接口,并定义数据访问方法,代码如下:

      package sso.dao;

      @Mapper

      public interface UserMapper {

      /**

      • 基于用户查询用户信息

      • @param username

      • @return 查询到的用户信息,表中的字段名会作为map中key,字段名对应的值会

      • 作为map中的value进行存储

      */

      @Select(“select * from sys_user where username=#{username}”)

      Map<String,Object> selectUserByUsername(

      @Param(“username”)String username);

      /**

      • 基于用户id查询用户权限信息

      */

      @Select(" select distinct m.permission " +

      " from sys_user u left join sys_user_role ur on u.id=ur.user_id " +

      " left join sys_role_menu rm on ur.role_id=rm.role_id " +

      " left join sys_menu m on rm.menu_id=m.id " +

      " where u.id=#{id}")

      List selectUserPermissions(@Param(“id”) Long id);

      }

      第二步:修改UserDetailServiceImpl类添加数据库访问操作,例如:

      package sso.service;

      /**

      • 通过此对象处理登录请求

      */

      @Service

      public class UserDetailServiceImpl implements UserDetailsService {

      @Autowired

      private BCryptPasswordEncoder passwordEncoder;

      @Autowired

      private UserMapper userMapper;

      @Override

      public UserDetails loadUserByUsername(String username)

      throws UsernameNotFoundException {

      //1.基于用户名查询用户信息

      Map<String,Object> userMap=userMapper.selectUserByUsername(username);

      if(userMap==null)throw new UsernameNotFoundException(“user not exists”);

      //2.查询用户权限信息并封装查询结果

      List userPermissions=

      userMapper.selectUserPermissions((Long)userMap.get(“id”));

      //权限信息后续要从数据库去查

      return new User(username,

      (String)userMap.get(“password”),//来自数据库

      AuthorityUtils.createAuthorityList(//来自数据库

      userPermissions.toArray(new String[]{})));

      //这个值返回给谁?谁调用此方法这个就返回给谁.

      }

      }

      自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

      深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

      因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

      既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

      由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

      如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

      结尾

      查漏补缺:Java岗 千+道面试题Java基础+全家桶+容器+反射+异常等

      这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。

      由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
      《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
      ption(“user not exists”);

      //2.查询用户权限信息并封装查询结果

      List userPermissions=

      userMapper.selectUserPermissions((Long)userMap.get(“id”));

      //权限信息后续要从数据库去查

      return new User(username,

      (String)userMap.get(“password”),//来自数据库

      AuthorityUtils.createAuthorityList(//来自数据库

      userPermissions.toArray(new String[]{})));

      //这个值返回给谁?谁调用此方法这个就返回给谁.

      }

      }

      自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

      深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

      因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-xiFdOKEh-1713784132819)]

      [外链图片转存中…(img-eisKn8cH-1713784132820)]

      [外链图片转存中…(img-to0BLWpS-1713784132820)]

      既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

      由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

      如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

      [外链图片转存中…(img-bOTaY8SZ-1713784132821)]

      结尾

      [外链图片转存中…(img-8MnVym6o-1713784132821)]

      这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。

      由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
      《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

    • 3
      点赞
    • 4
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    企业整合多个系统实现SSO(Single Sign-On)单点登录是为了提高用户体验和系统安全性而采取的一种解决方案。其核心思想是用户只需登录一次,就可以在多个系统中进行使用,无需再次输入登录凭证。 首先,前端设计需要进行用户认证和授权的处理。用户访问系统时,需要输入用户名和密码进行登录认证。前端需要将用户的登录凭证传递给后端进行验证。一般采用的认证方式有基于用户名和密码的表单登录、基于证书的登录等。认证成功后,前端需要生成一个令牌(token)并将其存储在客户端,以便后续在其他系统中进行验证。 其次,后端的实现需要提供认证和授权功能。后端系统需要验证用户提供的登录凭证,验证通过后会生成一个令牌,并将用户的登录凭证和令牌进行关联存储。后续用户在其他系统中访问时,可以通过令牌进行身份验证,无需再次输入登录凭证。同时,后端需要进行权限控制,确保用户在各个系统中只能访问其有权限的资源。 在实现过程中,需要使用一些技术和协议。常用的有SAML(Security Assertion Markup Language)、OAuth和OpenID Connect等。SAML是基于XML的身份验证和授权协议,可以实现不同域之间的单点登录。OAuth是一种授权框架,可以实现用户通过授权访问第三方应用程序的资源。OpenID Connect是基于OAuth的认证框架,可以实现用户在多个应用中的单点登录。 总结来说,企业整合多个系统实现SSO单点登录需要在前后端进行设计实现。前端需要进行用户认证和传递凭证,后端需要进行验证、令牌生成和权限控制。中间还需要使用一些技术和协议进行支持。通过SSO单点登录,可以提高用户体验和系统安全性,降低用户沉重的登录负担。

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值