Spring security基础学习

一、Spring security介绍

1、框架介绍
Spring 是一个非常流行的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)用户授权(Authorization)两个部分。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Spring Security其实就是用filter,多请求的路径进行过滤。

(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去。

2、认证与授权实现思路(Spring Security底层功能实现过程)
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
在这里插入图片描述

3、spring security 的核心功能主要包括
—认证 (你是谁)
—授权 (你能干什么)
—攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
在这里插入图片描述
比如:
(1)对于username password认证过滤器来说, 会检查是否是一个登录请求;是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;如果不满足则放行给下一个。
(2)下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有Authorization:Basic eHh4Onh4的信息。中间可能还有更多的认证过滤器。
(3)最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
注意:绿色的过滤器可以配置是否生效,其他的都不能控制。

二、Spring security快速入门

1、导入依赖
(1)此处我使用的是springboot引入starter完成的

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.chen</groupId>
    <artifactId>spring-security-demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-demo1</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- SpringSecurit -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <!-- SpringBoot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

(2)进入springsecurity的starter可以看到其核心jar:
在这里插入图片描述
2、通过配置类的方式实现security控制

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

     @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        //表单提交
        httpSecurity.formLogin()
                //当请求路径为 /login 时,程序会执行UserDetailsServiceImpl
                .loginProcessingUrl("/login")
                //自定义登录页面
                .loginPage("/login.html")
                //.successForwardUrl("/toMain")
                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
                //.failureForwardUrl("/toError");
                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        //授权认证
        httpSecurity.authorizeRequests()
                //放行登陆失败界面
                .antMatchers("/error.html").permitAll()
                //放行登录界面
                .antMatchers("/login.html").permitAll()
                //1.放行静态资源
                //.antMatchers("/js/**","/css/**","/images/**").permitAll()
                //2.放行所有后缀为png的资源
                //.antMatchers("/**/*.png").permitAll()
                //3.参数1表示当请求方式为GET时生效,参数2为正则表达式
                //.regexMatchers(HttpMethod.GET,".+[.]png").permitAll()
                //4.处理 spring.mvc.servlet.path=/xxx 指定项目访问路径时的放行:localhost:8080/xxx/demo不会再被拦截
                //.mvcMatchers("/demo").servletPath("/xxx").permitAll()//等价于.antMatchers("/xxx/demo").permitAll()
                //访问/main1.html需要指定权限:adminN
                //.antMatchers("/main1.html").hasAuthority("admiM")
                //多权限满足其一即可
                //.antMatchers("/main1.html").hasAnyAuthority("admin","admiN")
                //访问/main1.html需要指定角色:abc
                //.antMatchers("/main1.html").hasRole("abc")
                //多角色满足其一即可
                //.antMatchers("/main1.html").hasAnyRole("abc,abC")
                .antMatchers("/main1.html").hasIpAddress("127.0.0.1")
                //所有请求都必须被认证,必须登录之后被访问
                .anyRequest().authenticated();

        //关闭csrf防护
        httpSecurity.csrf().disable();
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

(1)内置访问控制方法介绍
在这里插入图片描述
A、permitAll:表示所匹配的 URL 任何人都允许访问。
B、denyAll:表示所匹配的 URL 都不允许被访问。
C、anonymous:表示可以匿名访问匹配的 URL。和 permitAll()效果类 似,只是设置为 anonymous()的 url 会执行到filter 链中。
D、authenticated():表示所匹配的 URL 都需要被认证才能访问。
E、fullyAuthenticated():如果用户不是被 remember me 的,才可以访问。
F、rememberMe():被“remember me”的用户允许访问。
(2)access()权限表达式

表达式描述
hasRole([role])当前用户是否拥有指定角色。
hasAnyRole([role1,role2])多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth])等同于hasRole
hasAnyAuthority([auth1,auth2])等同于hasAnyRole
Principle代表当前用户的principle对象
authentication直接从SecurityContext获取的当前Authentication对象
permitAll总是返回true,表示允许所有的
denyAll总是返回false,表示拒绝所有的
isAnonymous()当前用户是否是一个匿名用户
isRememberMe()表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated()表示当前用户是否已经登录认证成功了。
isFullyAuthenticated()如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

(3)@Secured:是专门用于判断是否具有角色的。能写在方法和类上,参数以ROLE_开头。
A、启用Security注解

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityDemo1Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityDemo1Application.class, args);
    }
}

B、在控制类上添加注解实现角色判断

@Controller
public class LoginController {
    //跳转成功页面的方法
    @Secured("ROLE_abc")
    @RequestMapping("toMain")
    public String toMain(){
        return "redirect:main.html";
    }
    //跳转失败页面的方法
    @RequestMapping("toError")
    public String toError(){
        return "redirect:error.html";
    }
}

:角色abc严格遵循大小写。ROLE_必须加便于系统识别角色abc

(4)@PreAuthorize/@PostAuthorize:都是方法或类级别的注解
@PreAuthorize:在方法或类执行之前判断权限。注解的参数和access()方法参数取值相同,都是权限表达式。
@PostAuthorize:在方法或类执行之后判断权限(使用场景很少)
A、启用PreAuthorize注解

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringSecurityDemo1Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityDemo1Application.class, args);
    }
}

B、在控制类上添加注解实现角色判断

@Controller
public class LoginController {
    //跳转成功页面的方法
    //@Secured("0abc")
    @PreAuthorize("hasRole('abc')")
    @RequestMapping("toMain")
    public String toMain(){
        return "redirect:main.html";
    }
    //跳转失败页面的方法
    @RequestMapping("toError")
    public String toError(){
        return "redirect:error.html";
    }
}

:角色abc严格遵循大小写。ROLE_可加也可不加,但是在配置文件中通过hasRole("abc")实现角色判断时不能加。

三、Spring security其他功能

1、RememberMe功能
“记住我”功能,用户只需要在登陆时添加remember-me复选框,取值为true。Spring Security会自动把用户信息存储到数据源中,以后就可以免登录访问。
(1)添加依赖
Spring Security实现Remember Me功能时底层实现依赖Spring-JDBC,所以需要导入Spring-JDBC。此处导入mybatis启动器同时添加mysql驱动。

    <!-- mybatis依赖 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <!-- mysql数据库依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.18</version>
    </dependency>

(2)配置数据源

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=xxxxxxxxx

(3)SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        //表单提交
        httpSecurity.formLogin()
                //当请求路径为 /login 时,程序会执行UserDetailsServiceImpl
                .loginProcessingUrl("/login")
                //自定义登录页面
                .loginPage("/login.html")
                .successForwardUrl("/toMain")
                .failureForwardUrl("/toError");
        //授权认证
        httpSecurity.authorizeRequests()
                //放行登陆失败界面
                .antMatchers("/error.html").permitAll()
                //放行登录界面
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated();

        //关闭csrf防护
        httpSecurity.csrf().disable();
        //异常处理
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);
		//remember-me
        httpSecurity.rememberMe()
                .userDetailsService(userDetailsService)
                .tokenRepository(persistentTokenRepository);
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动建表第一次启动需要,第二次启动需注释掉
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}

(4)修改登录界面,添加remember-me复选框:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录界面</title>
</head>
<body>
    <form action="/login" method="post">
        用户名:<input type="text" name="username" /><br/>
        密码:<input type="password" name="password" /><br/>
        记住我:<input type="checkbox" name="remember-me" value="true" /><br />
        <input type="submit" value="登录">&nbsp;&nbsp;&nbsp;&nbsp;<input type="reset" value="重置" />
    </form>
</body>
</html>

(5)第一次运行数据库会生成一张表:persisyent_logins
运行程序登录用户,勾选记住我,可以再数据表中查询到记录信息:
在这里插入图片描述
在这里插入图片描述
(6)当关闭浏览器或者网页在直接访问后台界面,就不会被登录界面所拦截。
在这里插入图片描述
(7)这个数据库默认失效时间为2周。也可以自行设置‘’记住我‘’失效时间:在这里插入图片描述
当差多超过60秒在访问main.html时,会被程序拦截,“记住我”登录状态失效。:在这里插入图片描述
2、Thymeleaf中SpringSecurity的使用
(1)引入jar包或依赖

<!-- thymeleaf springsecurity5 依赖 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!-- thymeleaf依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在html页面引入thymeleaf和security的命名空间

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

3、退出登录
(1)前端页面添加退出的超链接

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录成功界面</title>
</head>
<body>
<h2>登陆成功!</h2>
<a href="/logout">退出</a>
<a href="/main1.html">跳转</a>
</body>
</html>

(2)配置类设置退出登录映射url

//退出登录
httpSecurity.logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login.html");

(3)原理:通过addLogoutHandler()方法实现在这里插入图片描述
它的主要工作:在这里插入图片描述
4、SpringSecurity中的CSRF
(1)什么是CSRF
CSRF(Cross-site request forgery)跨站请求伪造,也称为“OneClick Attack”或者Session Riding。通过伪造用户请求访问受信任站点的非法请求。
跨域:只要网络协议,IP地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于http协议本身是无状态协议,所以引用cookie进行记录客户端身份。在cookie中会存放session id用来识别客户端身份的。在跨域的情况下,session id可能被第三方恶意挟持,通过这个session id向服务端发起请求时,服务端会认为这个请求是合法的,由此可能会引起很多意想不到的事情。
(2)SpringSecurity中的CSRF
从SpringSecurity4开始CSRF防护默认开启,默认会拦截请求,进行CSRF处理。CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。
A、打开csrf:在这里插入图片描述
默认是开启的。
B、当注释掉关闭csrf防护之后再次登录,发现权限不足:在这里插入图片描述
C、修改登录让其在登陆时携带服务器端生成的token:

<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}">

在这里插入图片描述
D、再次登录:在这里插入图片描述
F12通过控制台可以查看_csrf的值:在这里插入图片描述
一般正常登录会选择开启CSRF防护,登录表单需要携带隐藏域_csrf的服务器token值进行登录。
5、Oauth2r认证
(1)简介
第三方认证技术方案最主要是解决认证协议的通用标准问题,因为要实现跨系统认证,各系统之间要遵循一定的接口协议。
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务。
(2)下面分析一个Oauth2认证的例子,网站使用微信认证的过程:
在这里插入图片描述
(3)授权模式
A、授权码模式
a.引入依赖

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- oauth2 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <!-- 引入springcloud依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

b.UserService:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123");
        //不在引用security的User类,而是引用自定义的User类(此类也是实现了UserDetails接口)
        return new User("admin",password, AuthorityUtils
                .commaSeparatedStringToAuthorityList("admin"));
    }
}

c.User:

/**
 * 自定义一个User
 */
public class User implements UserDetails {
    private String userName;
    private String password;
    private List<GrantedAuthority> authorities;
    public User(String userName, String password, List<GrantedAuthority> authorities) {
        this.userName = userName;
        this.password = password;
        this.authorities = authorities;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return userName;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

d.UserController:

@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * 获取当前用户
     * @param authentication
     * @return
     */
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication){
        return authentication.getPrincipal();
    }
}

e.SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 密码加密方法
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        //采用BCryptPasswordEncoder类实现加密
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                //需要放行的请求
                .antMatchers("/oauth/**","/login/**","/logout/**").permitAll()
                //所有为放行请求必须认证
                .anyRequest().authenticated()
                .and()
                //表单认证全部放行
                .formLogin().permitAll();
    }
}

f.授权服务器

/**
 * 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //配置client-id
                .withClient("admin")
                //配置client-secret
                .secret("123")
                //设置访问token的有效期
                .accessTokenValiditySeconds(3600)
                //配置redirect_uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                //配置申请的权限范围
                .scopes("all")
                //配置grant_type,表示授权类型
                .authorizedGrantTypes("authorization_code");
    }
}

g.资源服务器

/**
 * 资源服务器配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**");
    }
}

h.测试:
获取授权码
http://localhost:8080/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
访问上述网址进入界面:在这里插入图片描述
账号密码为授权服务器配置的账号密码:
在这里插入图片描述
进入到对‘admin’是否授权的界面:在这里插入图片描述
选择允许后就会跳到百度主页:在这里插入图片描述
其中路径地址上参数code的值就表示授权码
然后我们就可以通过授权码去访问资源(因为我们的资源访问的controller类为post映射,所以此处用Postman发起请求):
设置好请求参数在这里插入图片描述
在这里插入图片描述
点击发送,就可以获取到令牌:在这里插入图片描述
根据令牌就可以请求资源:在这里插入图片描述
点击发送,就可以获取到返回的当前用户的信息:在这里插入图片描述
6、JWT
(1)常见的认证机制
A、HTTP Basic Auth
HTTP Basic Auth每次请求API时都提供用户的username和password。Basic Auth是配合RESTful API使用的最简单的认证方式,只需要提供用户名密码即可,但是由于存在暴露的风险,在生产环境使用越来越少。

B、Cookie Auth
Cookie认证机制jiushiwei就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器创建一个Cookie对象。通过客户端带上来的Cookie对象来与服务器端的Session对象匹配来实现状态管理。默认情况下浏览器关闭Cookie就会被删除(可以通过修改Cookie的expire time使其在一定的时间有效)。

C、OAuth
是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密资源(如照片、视频、联系人列表等),而无需将用户名和密码提供给第三方应用。
OAuth允许用户提供一个令牌来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统。(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:
在这里插入图片描述
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用。

D、Token Auth
使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。
a.客户端使用用户名和密码请求登录
b.服务端收到请求,去验证用户名和密码
c.验证成功后服务端会签发一个Token并把它发送给客户端
d.客户端收到Token之后把它存储起来,比如说放在Cookie中
e.客户端每次向服务端请求资源时需要带着此Token
f.服务端收到请求,通过验证Token决定是否返回数据
比A安全,比B节约服务器资源,比C轻量。

(2)JWT:JSON Web Token
A、JWT令牌的优点:
a.基于json方便解析
b.可以再令牌中自定义丰富的内容易拓展
c.通过非对称加密算法及数字签名技术,防篡改强安全性高
d.资源服务使用JWT可不依赖认证服务即可完成授权
缺点:JWT令牌较长,占存储空间大

B、JWT组成
实际上就是一个字符串,由头部、载荷和签名组成。

(3)JJWT:快速Demo
A、引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.chen</groupId>
    <artifactId>spring-security-jjwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-jjwt</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

B、测试生成一个token

@SpringBootTest
public class SpringSecurityJjwtApplicationTests {
    /**
     * 通过JWT生成token
     */
    @Test
    public void testCreateToken() {
        //创建JWT对象
        JwtBuilder jwtBuilder = Jwts.builder()
                //声明标识{"jti":"8888"}
                .setId("8888")
                //主体,用户{"sub":"Rose"}
                .setSubject("Rose")
                //创建日期{"ita":"当前时间"}
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"chen");
        //获取jwt的token
        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("=====================================");
        String[] split = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }
}

打印结果:在这里插入图片描述
头部和载荷是可以解密的,签名是无法解密的。

C、解析token

   /**
     * 解析token
     */
    @Test
    public void testParseToken(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTYwNTc1NTE0Nn0.gHkT__ZKeRMKdlGTgqi0qh5FG7dycTq_VXxve4ao8F8";
        Claims claims = Jwts.parser().setSigningKey("chen").parseClaimsJws(token).getBody();
        System.out.println("id:" + claims.getId());
        System.out.println("subject:" + claims.getSubject());
        System.out.println("issuedAt:" + claims.getIssuedAt());
    }

打印结果:在这里插入图片描述
D、token过期校验
a.生成一个token(带失效时间)

/**
     * 通过JWT生成token(带失效时间)
     */
    @Test
    public void testCreateTokenHasExp() {
        //当前系统时间
        long now = System.currentTimeMillis();
        //过期时间,2分钟
        long exp = now + 120*1000;
        //创建JWT对象
        JwtBuilder jwtBuilder = Jwts.builder()
                //声明标识{"jti":"8888"}
                .setId("8888")
                //主体,用户{"sub":"Rose"}
                .setSubject("Rose")
                //创建日期{"ita":"当前时间"}
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"chen")
                //设置过期时间
                .setExpiration(new Date(exp));
        //获取jwt的token
        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("=====================================");
        String[] split = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }

控制台打印:
在这里插入图片描述
b.解析这个token(带失效时间)

/**
     * 解析token(带失效时间)
     */
    @Test
    public void testParseTokenHasExp(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTYwNTc2NTI5NywiZXhwIjoxNjA1NzY1NDE3fQ.iV531DQAihdmbHPqLHylb6nUH1DR2AEZNzTL8sTQYVs";
        Claims claims = Jwts.parser().setSigningKey("chen").parseClaimsJws(token).getBody();
        System.out.println("id:" + claims.getId());
        System.out.println("subject:" + claims.getSubject());
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("签发时间:"+simpleDateFormat.format(claims.getIssuedAt()));
        System.out.println("过期时间:"+simpleDateFormat.format(claims.getExpiration()));
        System.out.println("当前时间:"+simpleDateFormat.format(new Date()));
    }

控制台打印:在这里插入图片描述
当超过2分钟在解析此token时,程序会抛出一个超时异常:在这里插入图片描述
E、自定义申明
a.生成token

/**
     * 通过JWT生成带自定义申明的token
     */
    @Test
    public void testCreateTokenByClaims() {
        //创建JWT对象
        JwtBuilder jwtBuilder = Jwts.builder()
                //声明标识{"jti":"8888"}
                .setId("8888")
                //主体,用户{"sub":"Rose"}
                .setSubject("Rose")
                //创建日期{"ita":"当前时间"}
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"chen")
                //自定义申明
                .claim("roles","admin")
                .claim("logo","xxx.jpg");
                //.addClaims(map);//可以将key-value键值对放入Map中在加到Claim中
        //获取jwt的token
        String token = jwtBuilder.compact();
        System.out.println(token);
        System.out.println("=====================================");
        String[] split = token.split("\\.");
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }

控制台打印:在这里插入图片描述
b.解析token

/**
     * 解析带自定义申明的token
     */
    @Test
    public void testParseTokenByClaims(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTYwNTc1NTE0Nn0.gHkT__ZKeRMKdlGTgqi0qh5FG7dycTq_VXxve4ao8F8";
        Claims claims = Jwts.parser().setSigningKey("chen").parseClaimsJws(token).getBody();
        System.out.println("id:" + claims.getId());
        System.out.println("subject:" + claims.getSubject());
        System.out.println("issuedAt:" + claims.getIssuedAt());
        System.out.println("roles:"+claims.get("roles"));
        System.out.println("logo:"+claims.get("logo"));
    }

控制台打印:在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值