五.单点登陆系统(SSO)设计及实现

系统简介

Http协议

web应用采用browser/server架构,http作为通信协议。http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关联,这个过程用下图说明,三次请求/响应对之间没有任何联系。

在这里插入图片描述

但这也同时意味着,任何用户都能通过浏览器访问服务器资源,如果想保护服务器的某些资源,必须限制浏览器请求;要限制浏览器请求,必须鉴别浏览器请求,响应合法请求,忽略非法请求;要鉴别浏览器请求,必须清楚浏览器请求状态。既然http协议无状态,那就让服务器和浏览器共同维护一个状态吧!这就是会话机制。

有状态会话

浏览器第一次请求服务器,服务器创建一个会话,并将会话的id作为响应的一部分发送给浏览器,浏览器存储会话id,并在后续第二次和第三次请求中带上会话id,服务器取得请求中的会话id就知道是不是同一个用户了,这个过程用下图说明,后续请求与第一次请求产生了关联。
在这里插入图片描述

服务器在内存中保存会话对象,浏览器怎么保存会话id呢?那就浏览器自己来维护这个会话id,每次发送http请求时浏览器自动发送会话id,cookie机制正好用来做这件事。cookie是浏览器用来存储少量数据的一种机制,数据以”key/value“形式存储,浏览器发送http请求时自动附带cookie信息
tomcat会话机制当然也实现了cookie,访问tomcat服务器时,浏览器中可以看到一个名为“JSESSIONID”的cookie,这就是tomcat会话机制维护的会话id,使用了cookie的请求响应过程如下图
在这里插入图片描述

记录登陆状态

有了会话机制,登录状态就好明白了,我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,正确的话说明当前持有这个会话的用户是合法用户,应该将这个会话状态进行保存,例如:

在这里插入图片描述

单点登录系统设计

概述

web系统早已从久远的单系统发展成为如今由多系统组成的应用群,面对如此众多的系统,用户难道要一个一个登录、然后一个一个注销吗?就像下图描述的这样,例如
在这里插入图片描述

Web系统由单系统发展成多系统组成的应用群,复杂性应该由系统内部承担,而不是用户。无论web系统内部多么复杂,对用户而言,都是一个统一的整体,也就是说,用户访问web系统的整个应用群与访问单个系统一样,登录/注销只要一次就够了
在这里插入图片描述
虽然单系统的登录解决方案很完美,但对于多系统应用群已经不再适用了,为什么呢?
单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器与服务器之间维护会话状态。但cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有cookie,例如:
在这里插入图片描述
既然这样,为什么不将web应用群中所有子系统的域名统一在一个顶级域名下,例如“*.baidu.com”,然后将它们的cookie域设置为“baidu.com”,这种做法理论上是可以的,甚至早期很多多系统登录就采用这种同域名共享cookie的方式。
然而,可行并不代表好,共享cookie的方式存在众多局限。首先,应用群域名得统一;其次,应用群各系统使用的技术(至少是web服务器)要相同,不然cookie的key值(tomcat为JSESSIONID)不同,无法维持会话,共享cookie的方式是无法实现跨语言技术平台登录的,比如java、php、.net系统之间;第三,cookie本身不安全。
因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录, 什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。

登陆业务设计

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建会话,例如:
在这里插入图片描述

创建项目聚合工程

创建聚合工程的目的是对项目中的资源(例如一些依赖)进行统一管理,多个项目module之间共享资源.
这次项目的maven工程结构如下:
|—04-jt-sso
|–sso-auth #认证服务器
|–sso-resource #资源服务器
|–pom.xml #公共依赖及版本管理

创建父工程

第一步:创建父工程,名字为04-jt-sso,例如:
在这里插入图片描述
第二步:删除工程中的src目录(parent工程一般不需要写java代码).
第三步:在pom.xml文件中添加parent元素并指定springboot依赖.

 <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.2.RELEASE</version>
</parent>

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

创建认证工程

第一步:在04-jt-sso工程下创建sso-auth项目module,例如
在这里插入图片描述
第二步:打开pom.xml文件,添加项目依赖

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第三步:创建application.yml配置文件,定义服务端口,例如

server:
    port: 8081

     
     
  • 1
  • 2
  • 1
  • 2

第四步:编写项目启动类.

package sso;
@SpringBootApplication
public class AuthApp {
    public static void main(String[] args) {
        SpringApplication.run(AuthApp.class,args);
    }
}

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第五步:启动服务检测是否可以启动ok.

创建资源工程

第一步:在04-jt-sso工程下创建sso-resource项目module,例如
在这里插入图片描述
第二步:打开pom.xml文件,添加项目依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第三步:创建application.yml配置文件,定义服务端口,例如

server:
  port: 8091

     
     
  • 1
  • 2
  • 1
  • 2

第四步:编写项目启动类.

package sso;
@SpringBootApplication
public class ResApp {
    public static void main(String[] args) {
        SpringApplication.run(ResApp.class,args);
    }
}

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

认证服务实现

工具类

第一步:定义JWT工具类,用于创建,解析,验证token,代码如下:

package sso.auth.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()+30*60*1000))
                .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());
    }
}

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

第二步:定义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();
    }
}

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

安全配置类

定义认证规则及异常处理,例如:

package sso.auth.config;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //1.关闭跨域攻击
        http.csrf().disable();
        //2.配置登录url(登录表单使用哪个页面)
        http.formLogin()
         .successHandler(authenticationSuccessHandler())
         .failureHandler(authenticationFailureHandler());
        //设置需要认证与拒绝访问的异常处理器
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint());
        //3.放行登录url(不需要认证就可以访问)
        http.authorizeRequests()
                .anyRequest().authenticated();//除了以上资源必须认证才可访问
    }
    //认证成功处理器
    public AuthenticationSuccessHandler authenticationSuccessHandler(){
        return (httpServletRequest, httpServletResponse,authentication)-> {
            User principal = (User)authentication.getPrincipal();
            Map<String,Object> map=new HashMap<>();
            map.put("state",200);
            map.put("message","Login ok");
            Map<String,Object> jwtMap=new HashMap<>();
            jwtMap.put("username", principal.getUsername());
            List<String> authorities = new ArrayList<>();
            principal.getAuthorities().forEach((authority)-> {
                    authorities.add(authority.getAuthority());
            });
            jwtMap.put("authorities",authorities);
            String token=JwtUtils.generatorToken(jwtMap);
            map.put("token", token);
            WebUtils.writeJsonToClient(httpServletResponse,map);
        };
    }
    //认证失败处理器
    public AuthenticationFailureHandler authenticationFailureHandler(){
        return (httpServletRequest, httpServletResponse, e) -> {
            Map<String,Object> map=new HashMap<>();
            map.put("state",500);
            map.put("msg","username or password error");
            WebUtils.writeJsonToClient(httpServletResponse,map);
        };
    }
    //没有认证时执行DefaultAuthenticationEntryPoint对象
    public AuthenticationEntryPoint authenticationEntryPoint(){
        return (httpServletRequest, httpServletResponse, e)->{
                Map<String,Object> map=new HashMap<>();
                map.put("state",401);//SC_UNAUTHORIZED 的值为401
                map.put("message","请先登录再访问");
                WebUtils.writeJsonToClient(httpServletResponse,map);
        };
    }
}

     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

认证逻辑对象

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

package sso.auth.service;

     
     
  • 1

@Service
public class UserDetailServiceImpl implements UserDetailsService {

<span class="token annotation punctuation">@Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">BCryptPasswordEncoder</span> passwordEncoder<span class="token punctuation">;</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token class-name">UserDetails</span> <span class="token function">loadUserByUsername</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">UsernameNotFoundException</span> <span class="token punctuation">{<!-- --></span>
        <span class="token comment">//1.基于用户名从数据库查询用户信息</span>
        <span class="token comment">//SysUser user=userMapper.selectUserByUsername(username);//查数据库</span>
        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token string">"jack"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment">//假设这是从数据库查询的信息</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UsernameNotFoundException</span><span class="token punctuation">(</span><span class="token string">"user not exists"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//2.将用户信息封装到UserDetails对象中并返回</span>
        <span class="token comment">//假设这个密码是从数据库查询出来的</span>
        <span class="token class-name">String</span> encodedPwd<span class="token operator">=</span>passwordEncoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token string">"123456"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//假设这个权限信息也是从数据库查询到的</span>
        <span class="token comment">//List&lt;String&gt; permissions=userMapper.selectUserPermissions(username);//查数据库</span>
        <span class="token comment">//假如分配权限的方式是角色,编写字符串时用"ROLE_"做前缀</span>
        <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">GrantedAuthority</span><span class="token punctuation">&gt;</span></span> grantedAuthorities <span class="token operator">=</span>
         <span class="token class-name">AuthorityUtils</span><span class="token punctuation">.</span><span class="token function">commaSeparatedStringToAuthorityList</span><span class="token punctuation">(</span> <span class="token string">"sys:res:retrieve,sys:res:create"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//这个user是SpringSecurity提供的UserDetails接口的实现,用于封装用户信息</span>
        <span class="token comment">//后续我们也可以基于需要自己构建UserDetails接口的实现</span>
        <span class="token class-name">User</span> user<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span>encodedPwd<span class="token punctuation">,</span>grantedAuthorities<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> user<span class="token punctuation">;</span><span class="token comment">//这里的返回值会交给springsecurity去校验</span>
    <span class="token punctuation">}</span>

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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

资源服务实现

工具类

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

package sso.resource.util;

   
   
  • 1

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());
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

第二步:定义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();
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

安全配置类

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

package sso.resource.config;

   
   
  • 1

@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);

    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

   
   
  • 1
  • 2

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

资源服务对象

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

package sso.resource.controller;
@RestController
public class ResourceController {
    @PreAuthorize("hasAuthority('sys:res:create')")
    @RequestMapping("/doCreate")
    public String doCreate(HttpServletResponse response){

   
   
    <span class="token keyword">return</span> <span class="token string">"create resource (insert data) ok"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**查询操作*/</span>
<span class="token annotation punctuation">@PreAuthorize</span><span class="token punctuation">(</span><span class="token string">"hasAuthority('sys:res:retrieve')"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/doRetrieve"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">doRetrieve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span><span class="token comment">//Retrieve 表示查询</span>
    <span class="token keyword">return</span> <span class="token string">"query resource (select data) ok"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**修改操作*/</span>
<span class="token annotation punctuation">@PreAuthorize</span><span class="token punctuation">(</span><span class="token string">"hasAuthority('sys:res:update')"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/doUpdate"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">doUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token string">"update resource (update data) ok"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**删除操作*/</span>
<span class="token annotation punctuation">@PreAuthorize</span><span class="token punctuation">(</span><span class="token string">"hasAuthority('sys:res:delete')"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/doDelete"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">doDelete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{<!-- --></span>
    <span class="token keyword">return</span> <span class="token string">"delete resource (dalete data) ok"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

Spring MVC拦截器

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

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

package sso.resource.interceptor;

   
   
  • 1

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<String> list = (List<String>) 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;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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

package sso.resource.config;

   
   
  • 1

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns("/**");
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

资源访问测试

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

在这里插入图片描述
第二步:启动资源服务器,并基于认证服务器返回的令牌进行资源访问
在这里插入图片描述

创建通用工程

背景分析

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

创建工程

![在这里插入图片描述](https://img-blog.csdnimg.cn/c4edc36646c14ab98e1089f05d6da896.png

初始化工程

第一步:添加项目依赖

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第二步:拷贝工具类
将sso-auth工程中的WebUtils,JwtUtils拷贝到sso-common工程的sso.util包中

创建跨域配置类

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

package sso.config;

   
   
  • 1

@Configuration
public class CorsFilterConfig {
/服务端过滤器层面的跨域设计/
@Bean
public FilterRegistrationBean<CorsFilter> 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<CorsFilter> fBean=
new FilterRegistrationBean(
new CorsFilter(configSource));
//设置此过滤器的优先级最高
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return fBean;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

引用通用工程

第一步:删除sso-auth,sso-resource 工程下的util包
第二步:删除sso-auth,sso-resource 工程中的公共依赖
第三步:在sso-auth,sso-resource工程中添加通用工程依赖

 <dependency>
        <groupId>com.cy.jt</groupId>
        <artifactId>sso-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

启动工程测试

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

创建前端工程

背景分析

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

创建前端工程

在这里插入图片描述

初始化工程

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

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

server:
  port: 80

   
   
  • 1
  • 2
  • 1
  • 2

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

package sso;
@SpringBootApplication
public class UIApplication {
    public static void main(String[] args) {
        SpringApplication.run(UIApplication.class,args);
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

创建静态页面

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

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

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

   
   
<span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> <span class="token class-name">Bootstrap</span> CSS <span class="token operator">--</span><span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>link href<span class="token operator">=</span><span class="token string">"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"</span> rel<span class="token operator">=</span><span class="token string">"stylesheet"</span> integrity<span class="token operator">=</span><span class="token string">"sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"</span> crossorigin<span class="token operator">=</span><span class="token string">"anonymous"</span><span class="token operator">&gt;</span>
<span class="token generics"><span class="token punctuation">&lt;</span>title<span class="token punctuation">&gt;</span></span>login<span class="token operator">&lt;</span><span class="token operator">/</span>title<span class="token operator">&gt;</span>

    
    
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

</head>
<body>
<div class=“container"id=“app”>
<h3>Please Login</h3>
<form>
<div class=“mb-3”>
<label for=“usernameId” class=“form-label”>Username</label>
<input type=“text” v-model=“username” class=“form-control” id=“usernameId” aria-describedby=“emailHelp”>
</div>
<div class=“mb-3”>
<label for=“passwordId” class=“form-label”>Password</label>
<input type=“password” v-model=“password” class=“form-control” id=“passwordId”>
</div>
<button type=“button” @click=“doLogin()” class=“btn btn-primary”>Submit</button>
</form>
</div>
<script src=“https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js” integrity=“sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM” crossorigin=“anonymous”></script>
<script src=“https://cdn.jsdelivr.net/npm/vue/dist/vue.js”></script>
<script src=“https://unpkg.com/axios/dist/axios.min.js”></script>
<script>
var vm=new Vue({
el:”#app",//定义监控点,vue底层会基于此监控点在内存中构建dom树
data:{ //此对象中定义页面上要操作的数据
username:"",
password:""
},
methods: { //此位置定义所有业务事件处理函数
doLogin() {
//1.定义url
let url = “http://localhost:8081/login”
//2.定义参数

            <span class="token keyword">var</span> params <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            params<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'username'</span><span class="token punctuation">,</span><span class="token keyword">this</span><span class="token punctuation">.</span>username<span class="token punctuation">)</span><span class="token punctuation">;</span>
            params<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">,</span><span class="token keyword">this</span><span class="token punctuation">.</span>password<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//3.发送异步请求</span>
            axios<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">{<!-- --></span>
               debugger
               <span class="token keyword">var</span> data<span class="token operator">=</span>response<span class="token punctuation">.</span>data<span class="token punctuation">;</span>
               console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">.</span>state <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"login ok"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                    window<span class="token punctuation">.</span>localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">"token"</span><span class="token punctuation">,</span>data<span class="token punctuation">.</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
                    location<span class="token punctuation">.</span>href<span class="token operator">=</span><span class="token string">"/index.html"</span>
                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span>
                    <span class="token function">alert</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

</script>
</body>
</html>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="appIndex">
<h1>Index Page <a href="#"  @click="doLogout()">Logout</a></h1>
<h2>CRUD(Create,Retrieve,Update,Delete) Operation</h2>
<ul>
    <li><a href="#" @click="doCreate()">Create(添加-insert)</a></li>
    <li><a href="#">Retrieve(查询-select)</a></li>
    <li><a href="#" @click="doUpdate()">Update(更新-update)</a></li>
    <li><a href="#">Delete(删除-delete)</a></li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    let vm=new Vue({
        el:"#appIndex",//定义监控点,vue底层会基于此监控点在内存中构建dom树
        methods: {//此位置定义所有业务事件处理函数
            doCreate() {
                //1.定义url
                let url = "http://localhost:8091/doCreate"
                //3.发送异步请求
                let token=localStorage.getItem("token");
                axios.get(url,{headers:{"token":token==null?"":token}}).then((response) => {
                   alert(response.data)
                })
            },
            doUpdate() {
                //1.定义url
                let url = "http://localhost:8091/doUpdate"
                //3.发送异步请求
                axios.get(url,{headers:{"token":localStorage.getItem("token")}}).then((response) => {
                    debugger
                    alert(response.data.message);
                })
            },
            doLogout() {
                //移除token
                localStorage.removeItem('token');
                //跳转到登录页面
                location.href="/login.html";
            },
        }
    });
</script>
</body>
</html>

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

工程访问测试

启动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;

   
   
  • 1
  • 2

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

use </span>jt_security<span class="token punctuation">;

CREATE TABLE </span>sys_menu<span class="token punctuation"> (
</span>id<span class="token punctuation"> bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,
</span>name<span class="token punctuation"> varchar(50) NOT NULL COMMENT ‘权限名称’,
</span>permission<span class="token punctuation"> varchar(200) DEFAULT NULL COMMENT ‘权限标识’,
PRIMARY KEY (</span>id<span class="token punctuation">) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘权限表’;
CREATE TABLE </span>sys_role<span class="token punctuation"> (
</span>id<span class="token punctuation"> bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘角色ID’,
</span>role_name<span class="token punctuation"> varchar(50) NOT NULL COMMENT ‘角色名称’,
PRIMARY KEY (</span>id<span class="token punctuation">) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色表’;
CREATE TABLE </span>sys_role_menu<span class="token punctuation"> (
</span>id<span class="token punctuation"> bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,
</span>role_id<span class="token punctuation"> bigint(11) DEFAULT NULL COMMENT ‘角色ID’,
</span>menu_id<span class="token punctuation"> bigint(11) DEFAULT NULL COMMENT ‘权限ID’,
PRIMARY KEY (</span>id<span class="token punctuation">) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色与权限关系表’;

CREATE TABLE </span>sys_user<span class="token punctuation"> (
</span>id<span class="token punctuation"> bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘用户ID’,
</span>username<span class="token punctuation"> varchar(50) NOT NULL COMMENT ‘用户名’,
</span>password<span class="token punctuation"> varchar(100) DEFAULT NULL COMMENT ‘密码’,
</span><span class="token keyword">status</span><span class="token punctuation"> varchar(10) DEFAULT NULL COMMENT ‘状态 PROHIBIT:禁用 NORMAL:正常’,
PRIMARY KEY (</span>id<span class="token punctuation">) USING BTREE,
UNIQUE KEY </span>username<span class="token punctuation"> (</span>username<span class="token punctuation">) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘系统用户表’;

CREATE TABLE </span>sys_user_role<span class="token punctuation"> (
</span>id<span class="token punctuation"> bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,
</span>user_id<span class="token punctuation"> bigint(11) DEFAULT NULL COMMENT ‘用户ID’,
</span>role_id<span class="token punctuation"> bigint(11) DEFAULT NULL COMMENT ‘角色ID’,
PRIMARY KEY (</span>id<span class="token punctuation">) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘用户与角色关系表’;

INSERT INTO </span>sys_menu<span class="token punctuation"> VALUES (1, ‘select users’, ‘sys:res:create’);
INSERT INTO </span>sys_menu<span class="token punctuation"> VALUES (2, ‘select menus’, ‘sys:res:retrieve’);
INSERT INTO </span>sys_menu<span class="token punctuation"> VALUES (3, ‘select roles’, ‘sys:res:delete’);
INSERT INTO </span>sys_role<span class="token punctuation"> VALUES (1, ‘ADMIN’);
INSERT INTO </span>sys_role<span class="token punctuation"> VALUES (2, ‘USER’);
INSERT INTO </span>sys_role_menu<span class="token punctuation"> VALUES (1, 1, 1);
INSERT INTO </span>sys_role_menu<span class="token punctuation"> VALUES (2, 1, 2);
INSERT INTO </span>sys_role_menu<span class="token punctuation"> VALUES (3, 1, 3);
INSERT INTO </span>sys_role_menu<span class="token punctuation"> VALUES (4, 2, 1);
INSERT INTO </span>sys_user<span class="token punctuation"> VALUES (1,‘admin’,’$2a

     10
    
   
   
    10
   
  
 </span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.64444em; vertical-align: 0em;"></span><span class="mord">1</span><span class="mord">0</span></span></span></span></span>hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’</span><span class="token punctuation">,</span><span class="token string">‘NORMAL’</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation"><code>&lt;/span&gt;sys_user&lt;span class="token punctuation"&gt;</code></span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token string">‘user’</span><span class="token punctuation">,</span><span class="token string">’$2a<span class="katex--inline"><span class="katex"><span class="katex-mathml">
 
  
   
    
     10
    
   
   
    10
   
  
 </span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.64444em; vertical-align: 0em;"></span><span class="mord">1</span><span class="mord">0</span></span></span></span></span>hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’</span><span class="token punctuation">,</span><span class="token string">‘NORMAL’</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation"><code>&lt;/span&gt;sys_user_role&lt;span class="token punctuation"&gt;</code></span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation"><code>&lt;/span&gt;sys_user_role&lt;span class="token punctuation"&gt;</code></span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span></p> 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

项目初始化

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

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第二步:修改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 #可以省略,默认会自动识别

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

业务代码编写

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

package sso.dao;

 
 
  • 1

@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);

<span class="token comment">/**
 * 基于用户id查询用户权限信息
 */</span>
<span class="token annotation punctuation">@Select</span><span class="token punctuation">(</span><span class="token string">" select distinct m.permission "</span> <span class="token operator">+</span>
        <span class="token string">" from sys_user u left join sys_user_role ur on u.id=ur.user_id "</span> <span class="token operator">+</span>
        <span class="token string">" left join sys_role_menu rm on ur.role_id=rm.role_id "</span> <span class="token operator">+</span>
        <span class="token string">" left join sys_menu m on rm.menu_id=m.id "</span> <span class="token operator">+</span>
        <span class="token string">" where u.id=#{id}"</span><span class="token punctuation">)</span>
<span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">&gt;</span></span> <span class="token function">selectUserPermissions</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Param</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> <span class="token class-name">Long</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

package sso.service;
/**
 * 通过此对象处理登录请求
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

 
 
<span class="token annotation punctuation">@Autowired</span>
<span class="token keyword">private</span> <span class="token class-name">UserMapper</span> userMapper<span class="token punctuation">;</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token class-name">UserDetails</span> <span class="token function">loadUserByUsername</span><span class="token punctuation">(</span><span class="token class-name">String</span> username<span class="token punctuation">)</span>
        <span class="token keyword">throws</span> <span class="token class-name">UsernameNotFoundException</span> <span class="token punctuation">{<!-- --></span>
    <span class="token comment">//1.基于用户名查询用户信息</span>
    <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span><span class="token class-name">Object</span><span class="token punctuation">&gt;</span></span> userMap<span class="token operator">=</span>userMapper<span class="token punctuation">.</span><span class="token function">selectUserByUsername</span><span class="token punctuation">(</span>username<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>userMap<span class="token operator">==</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UsernameNotFoundException</span><span class="token punctuation">(</span><span class="token string">"user not exists"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//2.查询用户权限信息并封装查询结果</span>
    <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">&gt;</span></span> userPermissions<span class="token operator">=</span>
    userMapper<span class="token punctuation">.</span><span class="token function">selectUserPermissions</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">Long</span><span class="token punctuation">)</span>userMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//权限信息后续要从数据库去查</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span>
            <span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">)</span>userMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token comment">//来自数据库</span>
            <span class="token class-name">AuthorityUtils</span><span class="token punctuation">.</span><span class="token function">createAuthorityList</span><span class="token punctuation">(</span><span class="token comment">//来自数据库</span>
                    userPermissions<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{<!-- --></span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//这个值返回给谁?谁调用此方法这个就返回给谁.</span>
<span class="token punctuation">}</span>

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

第三步,启动工程进行访问测试.

总结(summary)

重难点分析

  • 单体架构中的登录设计
  • 分布式架构中的单点登录设计
  • SpringSecutiry在认证服务器和资源服务器中的配置
  • JWT在认证授权系统中的应用

常见FAQ

  • 传统单体架构方式的会话是是如何实现的?(Cookie+Session)
  • 传统单体架构方式的登录在分布式架构中有什么缺陷?(cookie的跨域,session的共享)
  • 分布式架构中的认证方式如何实现?(方式1:Session数据持久化,方式2:认证服务器创建令牌,客户端
    存储令牌,资源服务端解析令牌)
  • 认证服务器用来做什么?(创建并响应令牌,设置认证机制-登录成功,失败,没有认证)
  • 认证服务器的令牌基于什么规范进行创建?(JWT-JSON Web Token)
  • 资源服务器你要做什么?(解析令牌,存储用户认证和权限信息,提供有条件的资源访问)
  • SpringBoot工程中编写单元测试要注意什么?(包-启动类所在包或子包,注解-@SpringBootTest,@Test-org.junit.jupiter.api.Test)
  • SecurityConfig的作用是什么?(配置认证规则,授权方式)
  • UserDetailsService接口的作用是什么?(访问数据库用户信息以及用户对应的权限信息,并进行封装,底层会交给AuthenticationManager管理器去进行认证.)
  • @EnableGlobalMethodSecurity 注解的作用是什么?(描述启动类或配置,用于告诉底层系统,假如方法上有 @PreAuthorize注解,则在方法层面启动权限检测,有权限则授权访问,没有权限则抛异常.)
  • 客户端拿到JWT令牌以后,如何进行的存储?(localStorage)
  • 客户端在访问资源服务时,如何将令牌传递到资源服务器?(将令牌放在ajax请求的请求头中)

BUG分析

  • 401 (访问资源时还没有认证)
  • 403 (访问资源时没有权限)
  • NullPointerException (对象访问属性或方法时因为对象为空而出现的异常)
  • ClassNotFoundException(类没有找到,假如不是自己类,检查对应的依赖.)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值