1. Spring Security

Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

2. 更多详情

关于 Spring Security

3.单点登录系统

单点登录,英文是 Single Sign On(缩写为 SSO)。即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通过该登录状态直接交互.

3.1 认证和授权时的一些关键技术

Spring Security,Jwt,Oauth2

4. 代码实现

4.1 步骤及注意事项:

1 . 添加依赖
2. 构建配置文件bootstrap.yml
3. 添加启动类
4. 自定义安全配置类(@Configuration),继承WebSecurityConfigurerAdapter

  • 创建密码加密对象 BCryptPasswordEncoder,并交给spring管理
  • 自定义认证规则–关闭跨域请求,放行所有请求,对成功和失败做处理
  • 创建返回值为AuthenticationSuccessHandler的成功方法,交给spring管理,编写处理代码
  • 创建返回值为AuthenticationFailureHandler的失败方法,交给spring管理,编写处理代码

5 . 自定义用户信息处理实现类(@Service),实现UserDetailsService接口

  • 实现接口,重写方法
  • 注入密码加密对象
  • 生成密码
  • 封装用户数据并返回

6 . 网关配置文件中添加配置

7 . 完成所有配置的组装

  • @AllArgsConstructor 全参构造
    @Configuration 配置类
    @EnableAuthorizationServer 开启认证和授权服务
  • 设置认证端点的配置
  • 定义令牌生成策略
  • 设置客户端详情
  • 认证成功后的安全约束配置

4.2 代码实现

4.2.1 添加依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

4.2.2 构建配置文件 bootstrap.yml

server:
  port: 8071
spring:
  application:
    name: sca-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848

4.2.3 启动类

package com.jt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author 小新
 * @date 2021/08/26 14:24
 */
@SpringBootApplication
public class ResourceAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResourceAuthApplication.class, args);
    }
}

4.2.4 自定义安全配置类

package com.jt.auth.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 小新
 * @date 2021/08/26 15:21
 */

/**
 * 自定义安全配置类,继承WebSecurityConfigurerAdapter
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 初始化密码加密对象
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义认证规则
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception{

        //关闭跨域攻击
        http.csrf().disable();
        //放行所有请求
        http.authorizeRequests().anyRequest().permitAll();

        //登录成功和失败的处理
        http.formLogin()
                .successHandler(successHandler())
                .failureHandler(failureHandler());

    }

    /**
     * 登录成功处理
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler successHandler(){
        return ( request,  response,  authentication) -> {
                //1.构建map对象,封装响应数据]
                Map<String,Object> map = new HashMap<>();
                map.put("state", 200);
                map.put("message", "login ok");
                //2.将map对象写到客户端
                writeJsonToClient(response,map);

        };
    }

    /**
     * 登录失败处理
     * @return
     */

    @Bean
    public AuthenticationFailureHandler failureHandler(){
        return (request, response,  e) -> {

            //1.构建map
            Map<String,Object> map = new HashMap<>();
            map.put("state", 500);
            map.put("message", "login failure");
            //2.将map写到客户端
            writeJsonToClient(response, map);


        };
    }

    /**
     * 将封装好的map转换成json然后写到客户端
     * @param response
     * @param object
     * @throws Exception
     */
    public void writeJsonToClient(HttpServletResponse response,Object object) throws IOException {

        //将对象转json
        String jsonStr = new ObjectMapper().writeValueAsString(object);
        //将json串写到客户端
        PrintWriter writer = response.getWriter();
        writer.println(jsonStr);
        writer.flush();

    }


}

4.2.5 定义用户信息处理对象

package com.jt.auth.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 *
 * @author 小新
 * @date 2021/08/26 15:51
 */

/**
 * 登陆时用户信息的获取和封装会在此对象进行实现
 * 在页面上点击登录按钮时,会调用这个对象的loadUserByUsername方法
 * 页面上输入的用户名会传给这个方法的参数
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    //UserDetails封装了用户信息(认证和权限信息)
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //1. 设置密码,
        String encodedPassword = passwordEncoder.encode("123456");
        //2. 查询用户权限信息(后面访问数据库,这里先给几个假数据)
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("sys.res.create", "sys.res.retrieve");

        //3. 对用户信息进行封装
        return new User(username, encodedPassword, authorities);
    }
}

4.2.6 网关配置文件中添加配置

- id: router02
   uri: lb://sca-auth  #lb表示负载均衡,底层默认使用ribbon实现
   predicates: #定义请求规则(请求需要按照此规则设计)
      - Path=/auth/oauth/**       #微服务下架构下,需要令牌
   filters:
      - StripPrefix=1 #转发之前去掉path中第一层路径

4.2.7 完成所有配置的组装

package com.jt.auth.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.Arrays;

/**
 * 完成所有配置的组装,在这个配置类中完成认证授权,JWT令牌签发等配置操作
 * 1)SpringSecurity (安全认证和授权)
 * 2)TokenConfig
 * 3)Oauth2
 * @author 小新
 * @date 2021/08/27 15:40
 */


@AllArgsConstructor
@Configuration
@EnableAuthorizationServer //开启认证和授权服务
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
    //此对象负责完成认证管理
    private AuthenticationManager authenticationManager;
    //TokenStore负责完成令牌创建,信息读取
    private TokenStore tokenStore;
    //负责获取用户详情信息(username,password,client_id,grant_type,client_secret)
    private ClientDetailsService clientDetailsService;
    //JWT令牌转换器(基于用户信息构建令牌,解析令牌)
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    //密码加密匹配器对象
    private PasswordEncoder passwordEncoder;
    //负责获取用户信息信息
    private UserDetailsService userDetailsService;

    //设置认证端点的配置(/oauth/token)
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            //配置认证管理器
            .authenticationManager(authenticationManager)
            //验证用户的方法获得用户详情
            .userDetailsService(userDetailsService)
            //要求提交认证使用post请求方式,提高安全性
            .allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET)
            //要配置令牌的生成,由于令牌生成比较复杂,下面有方法实现
            .tokenServices(tokenService());
    }

    //定义令牌生成策略
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        //这个方法的目标就是获得一个令牌生成器
        DefaultTokenServices services=new DefaultTokenServices();
        //支持令牌刷新策略(令牌有过期时间)
        services.setSupportRefreshToken(true);
        //设置令牌生成策略(tokenStore在TokenConfig配置了)
        services.setTokenStore(tokenStore);
        //设置令牌增强(固定用法-令牌Payload部分允许添加扩展数据,例如用户权限信息)
        TokenEnhancerChain chain=new TokenEnhancerChain();
        chain.setTokenEnhancers(
                Arrays.asList(jwtAccessTokenConverter));
        //令牌增强对象设置到令牌生成
        services.setTokenEnhancer(chain);
        //设置令牌有效期
        services.setAccessTokenValiditySeconds(3600);//1小时
        //刷新令牌应用场景:一般在用户登录系统后,令牌快过期时,系统自动帮助用户刷新令牌,提高用户的体验感
        services.setRefreshTokenValiditySeconds(3600*72);//3天
        //配置客户端详情
        services.setClientDetailsService(clientDetailsService);
        return services;
    }

    //设置客户端详情类似于用户详情
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            //客户端id
            .withClient("gateway-client")
            //客户端秘钥
            .secret(passwordEncoder.encode("123456"))
            //设置权限
            .scopes("all")//all只是个名字而已和写abc效果相同
            //允许客户端进行的操作  里面的字符串千万不能写错
            .authorizedGrantTypes("password","refresh_token");
    }
    // 认证成功后的安全约束配置
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //认证通过后,允许客户端进行哪些操作
        security
            //公开oauth/token_key端点
            .tokenKeyAccess("permitAll()")
            //公开oauth/check_token端点
            .checkTokenAccess("permitAll()")
            //允许提交请求进行认证(申请令牌)
            .allowFormAuthenticationForClients();
    }
}

图解

spring boot sso单点登录 token机制 单点登录 springsecurity_spring boot