栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

微服务---Spring Cloud Alibaba 07(微服务项目---单点登录)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

微服务---Spring Cloud Alibaba 07(微服务项目---单点登录)

        昨天我们的文件上传Demo已经比较完善了,今天我们来学习单点登录,更加的完善它,那么什么是单点登录呢?

背景

        传统的登录系统中,每个站点都实现了自己的专用登录模块。各站点的登录状态相互不认可,各站点需要逐一手工登录。这样的系统,我们又称之为多点登陆系统。应用起来相对繁琐(每次访问资源服务都需要重新登陆认证和授权)。与此同时,系统代码的重复也比较高。由此单点登陆系统诞生。

一、概述

百度百科:

 通俗易懂:

单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。单点登录在大型网站里使用得非常频繁,例如像阿里巴巴这样的网站,在网站的背后是成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都需要用户认证,不仅用户会疯掉,各子系统也会为这种重复认证授权的逻辑搞疯掉。实现单点登录说到底就是要解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性,因此要点也就以下两个:

  • 存储信任
  • 验证信任

二、实现

前面我们已经有了上传、网关、页面服务,接下来创建单点登录最重要的授权服务

1.添加pom依赖

web、oath2、nacosdiscovery、nacosconfig


        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
        
2.构建配置文件
server:
  port: 8071
spring:
  application:
    name: sca-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
3.项目启动类
@SpringBootApplication
public class ResourceAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResourceAuthApplication.class, args);
    }
}
4.启动项目

 项目启动时生成一个密码(oauth2的作用)

访问我们配置的端口号出现如下界面

 账号默认为user,密码就是项目启动时生成的密码,登录成功后会返回404,这表示登录成功,因为我们没有页面,这里我们可以自己配置一个页面,不过没啥必要

5.自定义登录

业务逻辑图:

        我们在实现登录时,会在UI工程中,定义登录页面(login.html),然后在页面中输入自己的登陆账号,登陆密码,将请求提交给网关,然后网关将请求转发到auth工程,登陆成功和失败要返回json数据,在这个章节我们会按这个业务逐步进行实现。

定义安全配置类

修改SecurityConfig配置类,添加登录成功或失败的处理逻辑

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //初始化加密对象
    //此对象提供了不可逆的加密方式,相对于MD5更加安全
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    //登录成功处理器
    @Bean
    public AuthenticationSuccessHandler successHandler(){
        return (request,response,authentication) ->{
            //1.构建map对象,封装响应数据
            Map map=new HashMap<>();
            map.put("state",200);
            map.put("message","login ok");
            //2.将map对象写到客户端
            writeJsonToClient(response,map);
        };
    }
    //登录失败处理器
    @Bean
    public AuthenticationFailureHandler failureHandler(){
        return (request,response, exception)-> {
            //1.构建map对象,封装响应数据
            Map map=new HashMap<>();
            map.put("state",500);
            map.put("message","login failure");
            //2.将map对象写到客户端
            writeJsonToClient(response,map);
        };
    }
    private void writeJsonToClient(HttpServletResponse response,Object object) throws IOException {
        //1.将对象转换为json
        //将对象转换为json有3种方案:
        //1)Google的Gson-->toJson  (需要自己找依赖)
        //2)阿里的fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
        //3)Springboot web自带的jackson-->writevalueAsString (spring-boot-starter-web)
        //我们这里借助springboot工程中自带的jackson
        //jackson中有一个对象类型为ObjectMapper,它内部提供了将对象转换为json的方法
        //例如:
        String jsonStr=new ObjectMapper().writevalueAsString(object);
        //3.将json字符串写到客户端
        PrintWriter writer = response.getWriter();
        writer.println(jsonStr);
        writer.flush();
    }
    //创建认证管理器对象,负责完成用户信息的认证,判定用户身份信息的合法性,在基于oauth2协议完成认证时,需要此对象,所以这里拿出来给spring管理
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

}
定义用户处理对象

处理用户对象首先要获取用户信息,而用户信息我们存在数据库中,所以我们要先设计数据库表

数据库设计:

这里我们就需要知道权限的设计思路,一般都是分为用户、角色、与权限三个部分,一个用户可以有多个角色,而一个角色又可以对应不同权限,分析上述我们最少应该创建用户信息表、用户角色对应表、角色表、角色权限对应表、权限表五张表,分析清楚了我们接下来进行建表

用户表

根据用户输入的账号密码进行比对判断是否可以登录 

角色表

权限表 

 用户角色对应表

 角色权限对应表

 数据库表我们就建好了,我们又怎么可以获取表中信息呢?所以我们要写一个feign接口,跨服务调用一下sql,进行用户信息和权限的查询,跨服务调用是因为整体的结构不过于混乱

创建新服务system负责数据库查询

1.添加pom依赖


        
        
        
            mysql
            mysql-connector-java
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.1
        
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-config
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
    

2.构建配置文件

server:
  port: 8061
spring:
  application:
    name: sca-system
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
  datasource:
    url: jdbc:mysql:///jt-sso?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: root

3.启动类

4.Pojo

@Data
//@TableName    自己写sql不用写这些
public class User implements Serializable {
    private static final long serialVersionUID = 4831304712151465443L;
    //@TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String status;

}

5.controller

@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("login/{username}")
    public User doSelectUserByUsername(@PathVariable ("username") String username){
        return userService.selectUserByUsername(username);
    }

    @GetMapping("permission/{userId}")
    public List doSelectUserPermissions(@PathVariable("userId") Integer userId){
        return userService.selectUserPermissions(userId);
    }

    @PostMapping("insert")
    public void doinsert(@RequestBody User user){
        userService.insert(user);
    }
}

查询用户信息、权限、和新增三部分

6.Service

@Service
public interface UserService {
    User selectUserByUsername(String username);
    List selectUserPermissions(Integer userId);
    void insert(User user);
}
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private UserMapper userMapper;
    @Override
    public User selectUserByUsername(String username) {
        if(username==null)return null;

        return userMapper.selectUserByUsername(username);
    }
    @Override
    public List selectUserPermissions(Integer userId) {
        return userMapper.selectUserPermissions(userId);
    }
    //注册
    @Override
    public void insert(User user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        user.setStatus("NORMAL");
        userMapper.insert(user);
    }
}

7.Mapper

@Mapper
public interface UserMapper extends baseMapper {
    
    @Select("select id,username,password,status " +
            "from tb_users " +
            "where username=#{username}")
    User selectUserByUsername(String username);
    
    @Select("select distinct m.permission " +
            "from tb_user_roles ur join tb_role_menus rm on ur.role_id=rm.role_id" +
            "     join tb_menus m on rm.menu_id=m.id " +
            "where ur.user_id=#{userId}")
    List selectUserPermissions(Integer userId);
}

MybatisPlus的最大缺点就是不能多表查询,所以我们用注解的方法写sql。

创建feign接口负责服务的调用
@FeignClient(name = "sca-system",contextId = "remoteUserService")//后面记得加访问失败时跳转的界面
public interface RemoteUserService {
    //查找用户信息
    @GetMapping("user/login/{username}")
    User selectUserByUsername(@PathVariable("username") String username);

    //查询权限
    @GetMapping("user/permission/{userId}")
    List selectUserPermissions(@PathVariable("userId") Integer userId);
}

用户处理对象的前置工作都做好了,我们接下来写用户处理对象

用户处理对象

        在spring security应用中底层会借助UserDetailService对象获取数据库信息,并进行封装,最后返回给认证管理器,完成认证操作,通过feign调用system服务

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private RemoteUserService remoteUserService;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.基于用户名查询用户信息(用户名、用户状态、密码)
        com.jt.auth.pojo.User user = remoteUserService.selectUserByUsername(username);
        if (user==null)
            throw new UsernameNotFoundException("用户不存在");
        //2.查询用户权限信息
        List permissions = remoteUserService.selectUserPermissions(user.getId());
        log.info("permission {}",permissions);
        System.out.println("permissions"+permissions);
        List authorityList = AuthorityUtils.createAuthorityList(permissions.toArray(new String[]{}));
        // 3.封装用户信息并返回
        //密码必须是加密后的
        System.out.println(user.getPassword());
        return new User(username, user.getPassword(), authorityList);
    }
}
构建令牌配置对象

        借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源.

@Configuration
public class TokenConfig {
    //JWT令牌签名时使用的密钥(盐值)
    
    private String SIGNING_KEY = "auth";

    //配置令牌的存储策略,对于oauth2规范中提供了集中策略
    
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    //定义Jwt转换器,负责生成jwt令牌,解析令牌内容,及验签方式
    
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
        //设置加密/解密口令
        
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}
整合类

完成所有配置的组装,在这个配置类中完成认证授权,JWT令牌签发等配置操作

SpringSecurity (提供认证和授权的实现)

TokenConfig(提供了令牌的生成,存储,校验方式)

Oauth2(定义了一套认证规范,例如为谁发令牌,都发什么内容,...)

继承AuthorizationServerConfigurerAdapter类,并重写里面的三个configure方法完成各项功能

@AllArgsConstructor  //lombok全参构造
@Configuration
@EnableAuthorizationServer //开启认证和授权服务
public class Oauth2Config extends AuthorizationServerConfigurerAdapter{
    //此对象负责完成认证管理
    private AuthenticationManager authenticationManager;
    //TokenStore负责完成令牌创建,信息读取
    private TokenStore tokenStore;
    //JWT令牌转换器(基于用户信息构建令牌,解析令牌)
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    //密码加密匹配器对象
    private PasswordEncoder passwordEncoder;
    //负责获取用户信息信息
    private UserDetailsService userDetailsService;
    //设置认证端点的配置(/oauth/token),客户端通过这个路径获取JWT令牌
    //去哪认证,认证细节配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        
        endpoints
                //配置认证管理器
                .authenticationManager(authenticationManager)
                //验证用户的方法获得用户详情
                .userDetailsService(userDetailsService)
                //要求提交认证使用post请求方式,提高安全性
                .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
                //要配置令牌的生成与存储,由于令牌生成比较复杂,下面有方法实现
                .tokenServices(tokenService());//这个不配置,默认令牌为UUID.randomUUID().toString()
    }
    //定义令牌生成策略
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        //这个方法的目标就是获得一个令牌生成器(此对象提供了创建、获取、刷新token的方法)
        DefaultTokenServices services=new DefaultTokenServices();
        //支持令牌刷新策略(令牌有过期时间)
        services.setSupportRefreshToken(true);
        //设置令牌生成存储策略(tokenStore在TokenConfig配置了,本次我们应用JWT-定义了一种令牌格式)
        services.setTokenStore(tokenStore);
        //设置令牌增强(允许设置令牌生成策略,默认是非jwt方式,没有业务数据,太简单,现在设置为jwt方式,并在令牌Payload部分允许添加扩展数据,例如用户权限信息)
        TokenEnhancerChain chain=new TokenEnhancerChain();
        chain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));//转换器
        services.setTokenEnhancer(chain);//令牌增强
        //设置令牌有效期
        services.setAccessTokenValiditySeconds(3600);//1小时
        //刷新令牌应用场景:一般在用户登录系统后,令牌快过期时,系统自动帮助用户刷新令牌,提高用户的体验感
        services.setRefreshTokenValiditySeconds(3600*72);//3天
        return services;
    }
    //给哪些客户端发送令牌,服务端规则定义
    //如限制高消费的人不能坐高铁
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //客户端id (客户端访问时需要这个id)
                .withClient("gateway-client")
                //客户端秘钥(客户端访问时需要携带这个密钥)
                .secret(passwordEncoder.encode("123456"))
                //设置权限
                .scopes("all")//all只是个名字而已和写abc效果相同
                //允许客户端进行的操作  这里的认证方式表示密码方式,里面的字符串千万不能写错
                .authorizedGrantTypes("password","refresh_token");
    }
    // 认证成功后的安全约束配置,对指定资源的访问放行,我们登录时需要访问/oauth/token,需要对这样的url进行放行
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //认证通过后,允许客户端进行哪些操作  参数permitAll()是官方定义好的
        
        security
                //公开oauth/token_key端点
                .tokenKeyAccess("permitAll()")
                //公开oauth/check_token端点
                .checkTokenAccess("permitAll()")
                //允许提交请求进行认证(申请令牌)
                .allowFormAuthenticationForClients();
    }
}
在服务端也添加TokenConfig配置令牌

用户登陆成功以后可以携带token访问服务端资源服务器,资源服务器中需要有解析token的对象

//在此配置类中配置令牌的生成,存储策略,验签方式(令牌合法性)。
@Configuration
public class TokenConfig {
    //JWT令牌签名时使用的密钥(盐值)
    
    private String SIGNING_KEY = "auth";

    //配置令牌的存储策略,对于oauth2规范中提供了集中策略
    
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    //定义Jwt转换器,负责生成jwt令牌,解析令牌内容,及验签方式
    
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
        //设置加密/解密口令
        
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}
启动和配置认证和授权规则

在服务端

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)  //启动方法上的权限控制,需要授权才可访问的方法上添加@PreAuthorize等相关注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {//资源服务配置拦截

    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //异常处理,可以省略
        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler());
        http.authorizeRequests()
                //.antMatchers("/resource/upload/**").authenticated()  // 限制访问
                .anyRequest().permitAll();

    }
}

继承ResourceServerConfigurerAdapter类重写configure方法

ResourceController 方法配置

在controller的上传方法上添加 @PreAuthorize(“hasAuthority(‘sys:res:create’)”)注解,用于告诉底层框架方法此方法需要具备的权限

    @PreAuthorize("hasAuthority('sys:res:create')")
    @RequiredLog("文件上传")//此注解描述的为切入点方法
    @PostMapping("upload")
    public String uploadFile(MultipartFile uploadFile) throws IOException {
测试

1.登录测试

 2.超级用户上传权限测试

 3.普通用户上传测试

 

  总结
  • 单点登陆解决方案:(市场常用两种: spring security+jwt+oauth2,spring securit+redis+oauth2)
  • Spring Security 是spring框架中的一个安全默认,实现了认证和授权操作
  • JWT是一种令牌格式,一种令牌规范,通过对JSON数据采用一定的编码,加密进行令牌设计
  • OAuth2是一种认证和授权规范,定义了单点登陆中服务的划分方式,认证的相关类型

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/309847.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号