SpringSecurity学习笔记

SpringSecurity学习笔记

一、SpringSecurity介绍:

1.Spring Security 是基于 Spring 的身份认证(Authentication)用户授权(Authorization)框架,提供了一 套 Web 应用安全性的完整解决方案。其中核心技术使用了 Servlet 过滤器、IOC 和 AOP 等

2.什么是身份认证

身份认证指的是用户去访问系统资源时,系统要求验证用户的身份信息,用户身份合法才访问对应资源。 常见的身份认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

3.什么是用户授权

当身份认证通过后,去访问系统的资源,系统会判断用户是否拥有访问该资源的权限,只允许访问有权限的 系统资源,没有权限的资源将无法访问,这个过程叫用户授权。
比如 会员管理模块有增删改查功能,有的用户只能进行查询,而有的用户可以进行修改、删除。一般来说, 系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

二、项目之间的依赖关系

父级工程:mengxuegu-security-parent

子模块:mengxuegu-security-base – 基础模块

​ mengxuegu-security-core – 核心依赖,springSecurtiy相关配置

​ mengxuegu-security-web – web服务模块,用于项目启动

依赖关系:web依赖core,

​ core依赖base

三、Spring-Security核心配置类

package com.mengxuegu.security.config;

import com.mengxuegu.security.authentication.CustomAuthenticationFailureHandler;
import com.mengxuegu.security.authentication.mobile.MobileAuthenticationConfig;
import com.mengxuegu.security.authentication.mobile.MobileValidateFilter;
import com.mengxuegu.security.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 安全控制中心
 *
 * @Author wangzw
 * @Date 2022/10/12 14:01
 */
@Configuration
@EnableWebSecurity // 启动SpringSecurity过滤器链功能
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解方法级权限控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private MobileValidateFilter mobileValidateFilter;

    @Autowired
    private UserDetailsService customUserDetailsService;

    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Autowired
    private MobileAuthenticationConfig mobileAuthenticationConfig;

    Logger logger = LoggerFactory.getLogger(getClass());

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }


    /**
     * 身份认证管理器
     * 1、认证信息提供方式(用户名、密码、当前用户的资源权限)
     * 2、可采用内存存储方式,也可采用数据库方式等
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 用户信息存储在内存中,这样控制台就没有自动密码了
//        String password = passwordEncoder().encode("1234");
//        logger.info("加密之后存储的密码:{}", password);
//        auth.inMemoryAuthentication()
//                .withUser("mengxuegu") // 用户名
//                .password(password) // 密码
//                .authorities("ADMIN"); // 权限标识
        auth.userDetailsService(customUserDetailsService);
    }

    /**
     * 资源权限配置(过滤器链)
     * 1、拦截的哪一些资源
     * 2、资源所对应的角色权限
     * 3、定义认证方式:httpBasic、httpForm
     * 4、定制登录页面、登录请求地址、错误处理方式
     * 5、自定义springSecurity过滤器等
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.httpBasic()
//        http.formLogin()    // 表单认证
//                .loginPage("/login/page")  // 交给/login/page 响应认证(登录)界面  (自定义登录界面)
//                .loginProcessingUrl("/login/form") // 登录表单提交处理url,默认是/login
//                .usernameParameter("name")  // 默认用户名的属性名是username
//                .passwordParameter("pwd") // 默认密码的属性名是password
//                .and()
//                .authorizeRequests() // 认证请求
//                .antMatchers("/login/page").permitAll()  // 放行跳转认证请求
//                .anyRequest() // 所有进入应用的http请求都要进行认证
//                .authenticated();

        http.addFilterBefore(mobileValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()    // 表单认证
                .loginPage(securityProperties.getAuthentication().getLoginPage())  // 交给/login/page 响应认证(登录)界面  (自定义登录界面)
                .loginProcessingUrl(securityProperties.getAuthentication().getLoginProcessingUrl()) // 登录表单提交处理url,默认是/login
                .usernameParameter(securityProperties.getAuthentication().getUsernameParameter())  // 默认用户名的属性名是username
                .passwordParameter(securityProperties.getAuthentication().getPasswordParameter()) // 默认密码的属性名是password
                .successHandler(customAuthenticationSuccessHandler) // 认证成功处理器
                .failureHandler(customAuthenticationFailureHandler) // 认证失败处理器
                .and()
                .authorizeRequests() // 认证请求
                .antMatchers(securityProperties.getAuthentication().getLoginPage(), "/code/image", "/mobile/page", "/code/mobile").permitAll()  // 放行跳转认证请求
                .anyRequest() // 所有进入应用的http请求都要进行认证
                .authenticated();

        http.csrf().disable();// 关闭跨站请求伪造

        // 将手机相关的配置绑定到过滤器链上
        http.apply(mobileAuthenticationConfig);
    }

    /**
     * 释放静态资源,核心过滤器配置方法
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
//        web.ignoring().antMatchers("/dist/**", "/modules/**", "/plugins/**");
        web.ignoring().antMatchers(securityProperties.getAuthentication().getStaticPaths());
    }
}

四、springsecurity修改中文错误提示文案

创建配置类ReloadMessageConfig,加载中文的认证提示信息到spring容器中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-URoLiIMT-1671776864272)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016142700311.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1Y80wyz-1671776864273)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221012160420191.png)]

可以通过抛出BadCredentialsException异常,来自定义异常信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgndVwBK-1671776864274)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115110139564.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPXXp8Up-1671776864274)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115110221224.png)]

五、动态认证用户信息

1.重点关注UserDetailsService、UserDetails接口

2.自定义一个UserDetailsService接口的实现类CustomUserDetailsService,实现该接口中的loadUserByUsername 方法 ,通过该方法定义获取用户信息的逻辑

从数据库获取到的用户信息封装到UserDetais
接口的实现类中(Spring Security 提供了一个org.springframework.security.core.userdetails.User实现类封装用户信息)。
如果未获取到用户信息
,则抛出异常throws UsernameNotFoundException

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Suo4ihXc-1671776864275)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016144048000.png)]

3.具体编码实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o50o0kIT-1671776864275)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016144130529.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CS8eS4WN-1671776864276)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016144241572.png)]

六、用户名密码认证流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCSNDynB-1671776864276)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016144602460.png)]

UsernamePasswordAuthenticationFilter#attemptAuthentication

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

    	// 从request中获取用户名密码
		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();

    	// 将username和password构造成一个UsernamePasswordAuthenticationToken实例,
    	// 其中构造器中会是否认证设置为authenticated=false
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
    	// 向authRequest对象中设置详细属性值。如添加了remoteAddress、sessionId值
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

ProviderManager#authenticate

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
    	// 获取当前的Authentication的认证类型
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

    	// 遍历认证提供者,不同认证方式有不同提供者,如:用户名密码认证提供者,手机短信认证提供者
		for (AuthenticationProvider provider : getProviders()) {
            // 选取当前认证方式对应的提供者
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
                // 进行认证操作
				result = provider.authenticate(authentication);

				if (result != null) {
                    // 认证通过的话,将认证结果的details赋值到当前认证对象authentication,然后跳出循环
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

AbstractUserDetailsAuthenticationProvider#authenticate

AbstractUserDetailsAuthenticationProvider是 AuthenticationProvider 的核心实现类

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
    	// 如果authentication不是UsernamePasswordAuthenticationToken类型,则抛出异常
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
    	// 获取用户名
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
    	// 从缓存中获取UserDetails
		UserDetails user = this.userCache.getUserFromCache(username);

    	// 当缓存中没有UserDetails,则从字类DaoAuthenticationProvider中获取
		if (user == null) {
			cacheWasUsed = false;

			try {
                // 子类DaoAuthenticationProvider中实现获取用户信息,
                // 就是调用UserDetailsService#loadUserByUsername
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
            // 前置检查。DefaultPreAuthenticationChecks检查账户是否锁定、是否可用、是否过期等
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
            // 异常则重新认证
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}
		
    	// 后置检查,由DefaultPostAuthenticationCheck实现(检测密码是否过期)
		postAuthenticationChecks.check(user);
		// 是否放到缓存中
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		// 将认证成功用户信息封装成UsernamePasswordAuthenticationToken对象并返回
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

七、手机短信验证码认证功能

实现流程:

手机号登录是不需要密码的,通过短信验证码实现免密登录功能

1.向手机发送手机验证码,使用第三方短信平台 SDK 发送,如: 阿里云短信服务(阿里大于)

2.登录表单输入短信验证码

3.使用自定义过滤器MobileValidateFilter

4.当验证码校验通过后,进入自定义手机认证过滤器MobileAuthenticationFilter校验手机号是否存在

5.自定义MobileAuthenticationToken提供给MobileAuthenticationFilter

6.自定义MobileAuthenticationProvider提供给ProviderManager 处理

7.创建针对手机号查询用户信息的MobileUserDetailsService,交给 MobileAuthenticationProvider

8.自定义 MobileAuthenticationConfig 配置类将上面组件连接起来,添加到容器中

9.将 MobileAuthenticationConfig 添加到 SpringSecurityConfig 安全配置的过滤器链上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1bMYVRa9-1671776864277)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221016150236445.png)]


SpringSecurityOAuth2.0

一、SpringSecurity OAuth2.0认证服务

1、OAuth2.0是什么??

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnIOVnR1-1671776864277)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115142459604.png)]

2、OAuth2.0涉及的角色

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SJoKAiK-1671776864277)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221013213349032.png)]

3、四种授权方式

1)、授权码模式:功能最完整,流程最严密的授权模式。国内各大服务提供商(微信、QQ、微 博、淘宝 、百度)都采用此模式进行授权。可以确定是用户真正同意授权;而且令牌是认证服务器发放给第三方应 用的服务器,而不是浏览器上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K48No5Sf-1671776864278)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221013213245237.png)]

2)、简化模式(Implicit): 令牌是发放给浏览器的,oauth客户端运行在浏览器中 ,通过JS脚本去申请令牌。而不是发放 给第三方应用的服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhVmfE4u-1671776864278)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221013213602500.png)]

**3)、密码模式(Resource Owner Password Credentials):**将用户名和密码传过去,直接获取 access_token 。用户 同意授权动作是在第三方应用上完成 ,而不是在认证服务器上。第三方应用申请令牌时,直接带着用户名密码去向 认证服务器申请令牌。这种方式认证服务器无法断定用户是否真的授权了,用户名密码可能是第三方应用盗取来的。

(重点关注,我们的系统就会用到此授权模式)

密码模式流程:

1.用户向客户端直接提供认证服务器平台的用户名和密码。

2.客户端将用户名和密码发给认证服务器,向后者请求令牌。

3.认证服务器确认无误后,向客户端提供访问令牌。

**4)、客户端证书模式(Client credentials):**用得少。当一个第三应用自己本身需要获取资源(而不是以用户的名 义),客户端模式十分有用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vlEE2dlv-1671776864278)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221013214137595.png)]

二、密码模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Pqtlt4T-1671776864279)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115205718296.png)]

认证服务器相关的配置

1、创建认证服务器配置类

package com.mengxuegu.oauth2.server.config;

import com.mengxuegu.oauth2.server.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 认证服务器配置
 *
 * @Author wangzw
 * @Date 2022/10/13 14:54
 */
@Configuration
@EnableAuthorizationServer // 开始oauth2认证服务器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * SpringSecurityConfig添加到容器中了
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Resource(name = "customUserDetailsService")
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    // 授权码管理策略
    @Bean
    public AuthorizationCodeServices jdbcAuthorizationCodeServices() {
        // JDBC方式保存授权码到oauth_code表中
        // 意义不大,因为获取一次令牌后,授权码就失效了
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    // jdbc管理客户端信息
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }


    /**
     * 配置被允许访问此认证服务器的客户端详情信息
     * 方式1:内存方式管理
     * 方式2:数据库管理
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用内存方式
//        clients.inMemory()
        		  // 允许访问此认证服务器的客户端id,如:PC,APP,小程序等,各自不同的客户端id
//                .withClient("mengxuegu-pc") // 客户端id
//                // 客户端密码,要加密,不然一直要求登录,获取不到令牌,而且一定不能被泄露
//                .secret(passwordEncoder.encode("mengxuegu-secret"))
//                // 资源id,如商品资源
//                .resourceIds("product-server")
//                // 授权类型,可同时支持多种授权类型
//                .authorizedGrantTypes("authorization_code", "password", "implicit", "client_credentials", "refresh_token")
//                // 授权范围标识,如指定微服务名称,则只能访问指定的微服务
//                .scopes("all")
//                // false跳转到授权页面手动点击授权,true不用手动授权,直接响应授权码
//                .autoApprove(false)
//                // 客户端回调地址
//                .redirectUris("http://www.mengxuegu.com/");

        // 使用JDBC管理客户端信息
        clients.withClientDetails(jdbcClientDetailsService());
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 密码模式要设置认证管理器
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌获取新令牌时需要
        endpoints.userDetailsService(customUserDetailsService);
        // 令牌管理策略(将令牌管理策略作用到认证服务器端点上)
//        endpoints.tokenStore(tokenStore);
        // jwt令牌管理策略
        endpoints.tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter);
        // 授权码管理策略,针对授权码模式有效,会将授权码放到auth_code表,授权后就会被删除
        endpoints.authorizationCodeServices(jdbcAuthorizationCodeServices());
    }

    /**
     * 令牌端点的安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 所有人可访问/oauth/token_key后面要获取公钥,默认拒绝访问
        security.tokenKeyAccess("permitAll()");
        // 认证后可访问/oauth/check_token 默认拒绝访问
        security.checkTokenAccess("isAuthenticated()");
    }
}



2、安全配置类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6yj5SYcx-1671776864279)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115204211644.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qpnwOQh6-1671776864280)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221014152423906.png)]

三、令牌管理策略Redis&JDBC

概述

默认情况下,令牌是通过 randomUUID 产生32位随机数的来进行填充的,而产生的令牌默认是存储在内存中。
内存存储采用的是 TokenStore 接口的默认实现类 InMemoryTokenStore , 开发时方便调试,适用单机版。
RedisTokenStore 将令牌存储到 Redis 非关系型数据库中,适用于并发高的服务。
JdbcTokenStore 基于 JDBC 将令牌存储到 关系型数据库中,可以在不同的服务器之间共享令牌。
JwtTokenStore (JSON Web Token)将用户信息直接编码到令牌中,这样后端可以不用存储它,前端拿到令
牌可以直接解析出用户信息

1、redis管理令牌

配置步骤:

1)、创建TokenConfig令牌管理策略类


/**
 * @Author wangzw
 * @Date 2022/10/13 21:48
 */
@Configuration
public class TokenConfig {

    /**
     * redis管理令牌
     * 1.启动redis服务器
     * 2.添加redis相关依赖
     * 3.添加redis依赖后,容器就会有RedisConnectionFactory实例
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public TokenStore tokenStore() {
        // Redis管理令牌
        return new RedisTokenStore(redisConnectionFactory);
    }
}

2)、令牌管理策略添加到端点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGyQvpAw-1671776864280)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115211912236.png)]

3)、使用密码模式获取token

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WvKpEDW-1671776864280)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115211953267.png)]

4)、查看redis管理工具

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NujxyFDi-1671776864281)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115212035619.png)]

2、JDBC管理令牌

配置方式:

1)、令牌管理策略类TokenConfig,使用JDBC方式去管理令牌

/**
 * @Author wangzw
 * @Date 2022/10/13 21:48
 */
@Configuration
public class TokenConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    @Bean
    public TokenStore tokenStore() {
        // JDBC管理令牌
        return new JdbcTokenStore(dataSource());
    }

}

2)、将令牌配置策略类应用到端点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEPk2Cva-1671776864281)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115211912236.png)]

3)、测试用密码模式获取token,观察数据库表oauth_access_token会多出一条记录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TeJz4mZo-1671776864281)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115213502396.png)]

资源服务器相关配置

概述

资源服务器实际上就是对系统功能的增删改查,比如:商品管理、订单管理、积分管理、会员管理等资源,而在微
服务架构中,而这每个资源实际上就是每一个微服务。当用户请求某个微服务资源时,首先通过认证服务器进行认
证与授权,通过后再才可访问到对应资源。

实现的功能:

  • 要让他知道自己是资源服务器,他知道这件事后,才会在前边加一个过滤器去验令牌(配置
    @EnableResourceServer 配置类)
  • 要让他知道自己是什么资源服务器(配置资源服务器ID) ,配置去哪里验令牌,怎么验令牌,要带什么信息去验
  • 进行资源的安全配置,让他知道资源的每个访问权限是什么
/**
 * 资源服务器相关配置
 *
 * @Author wangzw
 * @Date 2022/10/14 11:00
 */
@Configuration
@EnableResourceServer // 标识为资源服务器,所有发往这个服务的请求,都会去请求头里找token,找不到或者通过认证服务器验证不合法,则无法访问资源
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级权限控制
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    // 配置当前资源服务器的ID
    public static final String RESOURCE_ID = "product-server";

    @Autowired
    private TokenStore tokenStore;

    /**
     * 当前资源服务器的一些配置,如 资源服务器ID
     *
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(RESOURCE_ID) // 配置当前资源服务器的ID,会在认证服务器验证(客户端表resources配置了就可以访问这个服务)
//                .tokenServices(tokenService());   // 实现令牌服务,ResourceServerTokenServices实例
                .tokenStore(tokenStore);
    }

//    /**
//     * 配置资源服务器如何验证token有效性
//     * 1. DefaultTokenServices
//     * 如果认证服务器和资源服务器同一服务时,则直接采用此默认服务验证即可
//     * 2. RemoteTokenServices (当前采用这个)
//     * 当认证服务器和资源服务器不是同一服务时, 要使用此服务去远程认证服务器验证
//     * @return
//     */
//    @Bean
//    public ResourceServerTokenServices tokenService() {
//        // 资源服务器去远程认证服务器验证token是否有效
//        RemoteTokenServices service = new RemoteTokenServices();
//
//        // 请求认证服务器验证URL,注意:默认这个端点是拒绝访问的,要设置认证后可访问
//        service.setCheckTokenEndpointUrl("http://localhost:8090/auth/oauth/check_token");
//
//        // 在认证服务器配置的客户端id
//        service.setClientId("mengxuegu-pc");
//
//        // 在认证服务器配置的客户端密码
//        service.setClientSecret("mengxuegu-secret");
//
//        return service;
//    }

    /**
     * 控制令牌范围权限和授权规则
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                // SpringSecurity不会创建也不会使用HttpSession
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 所有的请求对应访问的用户都要有all范围权限
                .antMatchers("/**").access("#oauth2.hasScope('all')");
    }
}

四、JWT令牌

1、JWT解决的问题

当认证服务器和资源服务器不是在同一工程时, 要使用 ResourceServerTokenServices 去远程请求认证服务器来校验
令牌的合法性,如果用户访问量较大时将会影响系统的性能

解决方式:

生成令牌采用 JWT 格式就可以解决上面的问题。
因为当用户认证后获取到一个JWT令牌,而这个 JWT 令牌包含了用户基本信息,客户端只需要携带JWT访问资源服
务器,资源服务器会通过事先约定好的算法进行解析出来,然后直接对 JWT 令牌校验,不需要每次远程请求认证服
务器完成授权。

2、JWT是什么

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRzVImOL-1671776864282)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115215735286.png)]

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicHJvZHVjdC1zZXJ2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJhbGwiLCJQUk9EVUNUX0FQSSJdLCJleHAiOjE2Njg1NzA2MzQsImF1dGhvcml0aWVzIjpbInN5czp1c2VyOmxpc3QiLCJwcm9kdWN0Omxpc3QiLCJzeXM6bWFuYWdlIiwic3lzOnVzZXIiLCJzeXM6cGVybWlzc2lvbjphZGQiLCJzeXM6aW5kZXgiLCJzeXM6dXNlcjphZGQiLCJzeXM6cGVybWlzc2lvbjplZGl0Iiwic3lzOnJvbGU6ZWRpdCIsInN5czpwZXJtaXNzaW9uIiwic3lzOnVzZXI6ZWRpdCIsInN5czp1c2VyOmRlbGV0ZSIsInN5czpyb2xlOmRlbGV0ZSIsInN5czpyb2xlOmxpc3QiLCJzeXM6cGVybWlzc2lvbjpkZWxldGUiLCJzeXM6cGVybWlzc2lvbjpsaXN0Iiwic3lzOnJvbGUiLCJzeXM6cm9sZTphZGQiXSwianRpIjoiYTg5YzBjYzQtNmI4Zi00ZjgzLTk4YzAtNDRmMWRmNmUyZGUwIiwiY2xpZW50X2lkIjoibWVuZ3h1ZWd1LXBjIn0.IwDTSQo0c8sJ10gHk53tSmT-Awug4mAnAknOm9K3Poks4RiTaVZRozVtQMXWv_Ni6WGZNPQhGyzHxrUcPOWvPU5F8cbRNxhKIa4gpdPtPhjTvgA55h-i789jVYSVE1NtH5VfJeD3UVrwCV3zuu4cbFYaXekRCR0WSv9qQiMuMzinKpH1q3jWPTqu7zZ_j32KMTId0E0WV_nSX5B2KkfCc2Kjz-gNjtp8pz4Vir8heXqcSomqAez4u_2T0diBmV0vRr_avTvhbj9rCwLXPmAuemRbepY3jNa8NKbssOK9dXM-tubbUwGYo1IfWt94INWJ2ZxYeFqURZCPRFtA1FBwNQ",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicHJvZHVjdC1zZXJ2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJhbGwiLCJQUk9EVUNUX0FQSSJdLCJhdGkiOiJhODljMGNjNC02YjhmLTRmODMtOThjMC00NGYxZGY2ZTJkZTAiLCJleHAiOjE2NzExMTI2MzQsImF1dGhvcml0aWVzIjpbInN5czp1c2VyOmxpc3QiLCJwcm9kdWN0Omxpc3QiLCJzeXM6bWFuYWdlIiwic3lzOnVzZXIiLCJzeXM6cGVybWlzc2lvbjphZGQiLCJzeXM6aW5kZXgiLCJzeXM6dXNlcjphZGQiLCJzeXM6cGVybWlzc2lvbjplZGl0Iiwic3lzOnJvbGU6ZWRpdCIsInN5czpwZXJtaXNzaW9uIiwic3lzOnVzZXI6ZWRpdCIsInN5czp1c2VyOmRlbGV0ZSIsInN5czpyb2xlOmRlbGV0ZSIsInN5czpyb2xlOmxpc3QiLCJzeXM6cGVybWlzc2lvbjpkZWxldGUiLCJzeXM6cGVybWlzc2lvbjpsaXN0Iiwic3lzOnJvbGUiLCJzeXM6cm9sZTphZGQiXSwianRpIjoiYmZiODNkYmMtNzhhOS00MmY3LTg1ZTktMjNkYTA3NGRhNWNhIiwiY2xpZW50X2lkIjoibWVuZ3h1ZWd1LXBjIn0.iQZs8A-Oasys5za_QX2Y91m3I0fPOQpEbOAX6HJo8A-z4AwAUWVvI6sis_dj_rDioZocTjW_3ZIISMArMhT-0vOLmLyu_VP9ryIz_Fl60OM7GJ9JTPNl8Gic4RCYSG7cQIx9IYsNATCGEu_hNR0eswyMi_vyvaTKie4oerLk5r2JZeC2DquXqPmiVHOx5HjgRWOjpoW2nfEsekqcNHj8eSK372x5V3J3SxOEgsxYV5lSSEB_J1KbZ16vT-oAsadi9MpQzL7hoTEDdbF5urRYY6jLp4-NU-7A0ICHjHwslJKH7BnEv44LgYntQuqND8FX6rL_RItDakGEWWtLZBZ5fg",
    "expires_in": 49999,
    "scope": "all PRODUCT_API",
    "jti": "a89c0cc4-6b8f-4f83-98c0-44f1df6e2de0"
}

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之
间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC
法)或使用RSAECDSA的公钥/私钥对进行签名 ,防止被篡改。
JWT 官网: https://jwt.io 想深入了解的可网站查看
JWT **的构成:
**JWT 有三部分构成:头部、有效载荷、签名
例如:aaaaa.bbbbbb.cccccccc
**头部:**包含令牌的类型(JWT) 与加密的签名算法((如 SHA256 或 ES256) ,Base64编码后加入第一部分
有效载荷:通俗一点讲就是token中需要携带的信息都将存于此部分,比如:用户id、权限标识等信息。
注:该部分信息任何人都可以读出来,所以添加的信息需要加密才会保证信息的安全性
**签名:**用于防止 JWT 内容被篡改, 会将头部和有效载荷分别进行 Base64编码,编码后用 . 连接组成新的字符
串,然后再使用头部声明的签名算法进行签名。在具有秘钥的情况下,可以验证JWT的准确性,是否被篡改

3、JWT的优缺点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYQY7wil-1671776864282)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115153759370.png)]

4、JWT管理token方式

配置:

1)、在认证服务器的TokenConfig管理策略类中

/**
 * @Author wangzw
 * @Date 2022/10/13 21:48
 */
@Configuration
public class TokenConfig {

    /**
     * 在JwtAccessTokenConverter中定义Jwt签名密码
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 读取oauth2.jks文件中的私钥,第二个参数是口令oauth2
        KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "oauth2".toCharArray());
        // 别名oauth2
        converter.setKeyPair(keyFactory.getKeyPair("oauth2"));
        return converter;
    }


    @Bean
    public TokenStore tokenStore() {
        // JWT管理令牌
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
}

2)、将令牌配置策略应用到端点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnRolf8d-1671776864282)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115221041407.png)]

3)、使用密码模式来获取token

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQGLzWY5-1671776864283)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115221151894.png)]

4)、检查JWT令牌,包含了用户信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VVmSzQt6-1671776864283)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115221233145.png)]

五、Spring Cloud OAuth2分布式认证授权

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lw9tAnpS-1671776864283)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115154942521.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Id0nxsNi-1671776864284)(C:\Users\wangzw\AppData\Roaming\Typora\typora-user-images\image-20221115163650723.png)]

http://localhost:7001/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=code

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    // 读取oauth2.jks文件中的私钥,第二个参数是口令oauth2
    KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "oauth2".toCharArray());
    // 别名oauth2
    converter.setKeyPair(keyFactory.getKeyPair("oauth2"));
    return converter;
}


@Bean
public TokenStore tokenStore() {
    // JWT管理令牌
    return new JwtTokenStore(jwtAccessTokenConverter());
}

}




**2)、将令牌配置策略应用到端点**

[外链图片转存中...(img-gnRolf8d-1671776864282)]



**3)、使用密码模式来获取token**

[外链图片转存中...(img-YQGLzWY5-1671776864283)]



**4)、检查JWT令牌,包含了用户信息**

[外链图片转存中...(img-VVmSzQt6-1671776864283)]



### 五、Spring Cloud OAuth2分布式认证授权

[外链图片转存中...(img-lw9tAnpS-1671776864283)]



[外链图片转存中...(img-Id0nxsNi-1671776864284)]

http://localhost:7001/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=code  

你可能感兴趣的:(学习,servlet,java)