OAuth2.0+SpringSecurity+Jwt实现系统的登录认证,用户授权

系统的登录认证、用户授权实现

本次从以下几个内容进行分享:

  1. 使用Jwt生成公钥和私钥
  2. 根据Jwt令牌实现登录认证
  3. 完成用户的鉴权和授权
  4. 自定义项目跳转的登录页面

这是本次实现的流程图:

在这里插入图片描述

使用JWT生成公钥和私钥

JWT令牌生成采用非对称加密算法

1、生成密钥证书
下边命令生成密钥证书,采用RSA算法每个证书包含公钥和私钥

keytool -genkeypair -alias changgou -keyalg RSA -keypass changgou -keystore changgou.jks -storepass changgou

Keytool 是一个java提供的证书管理工具
-alias : 密钥的别名
-keyalg :使用的hash算法
-keypass :密钥的访问密码
-keystore :密钥库文件名,xc.keystore保存了生成的证书
-storepass :密钥库的访问密码

查询证书信息:

keytool -list -keystore changgou.jks 

2、导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装 openssl:http://slproweb.com/products/Win32OpenSSL.html 安装资料目录下的Win64OpenSSL-1_1_1b.exe 配置openssl的path环境变量,
cmd进入changgou.jks文件所在目录执行如下命令:

keytool -list -rfc --keystore changgou.jks | openssl x509 -inform pem -pubkey 

公钥:

-----BEGIN PUBLIC KEY----- 
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAm t47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnh cP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEm oLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/ iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZS xtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv 9QIDAQAB 
-----END PUBLIC KEY----- 

将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。

根据Jwt令牌实现登录认证
  • 准备工作:
    • 数据库准备相应的表
      在这里插入图片描述

    • 准备配置类
      在这里插入图片描述

    • 配置文件

server:
  port: 9200
spring:
  application:
    name: user-oauth
  cloud:
    nacos:
      discovery:
        server-addr: ${ys.nacos.server}
  datasource:
    url: ${ys.ds.userurl}
    driver-class-name: ${ys.ds.driver}
#    url: jdbc:mysql://192.168.23.170:3305/yinjoer_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: ${ys.ds.username}
    password: ${ys.ds.password}
  redis:
      host: ${ys.redis}
encrypt:
  key-store:
    location: classpath:/changgou.jks
    secret: changgou
    alias: changgou
    password: changgou
auth:
  ttl: 3600  #token存储到redis的过期时间
  clientId: changgou
  clientSecret: changgou
  cookieDomain: localhost
  cookieMaxAge: -1
  • 具体实现
    1.Service接口
@Component
public interface AuthService {

    /**
     * 
     * @param username 用户名
     * @param password 用户密码
     * @param clientId 客户端Id
     * @param clientsecret 客户端密码
     * @return
     */
    AuthToken login(String username, String password, String clientId, String clientsecret);
}

2.Service实现类

/**
 * 认证实现类
 * @author ys
 */
@Service
@Transactional
public class AuthServiceImpl implements AuthService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${auth.ttl}")
    private Long ttl;

    @Override
    public AuthToken login(String username, String password, String clientId, String clientsecret) {
        ServiceInstance instance = loadBalancerClient.choose("user-oauth");
        URI uri = instance.getUri();
        String url = uri+"/oauth/token";


        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type","password");
        body.add("username", username);
        body.add("password", password);

        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("Authorization", this.getHttpBasic(clientId, clientsecret));
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });

        ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Map.class);
        Map responseEntityBody = responseEntity.getBody();
        if (responseEntityBody == null || responseEntityBody.get("access_token") == null ||
                responseEntityBody.get("jti") == null || responseEntityBody.get("refresh_token") == null) {
            throw new RuntimeException("申请令牌失败");
        }

        AuthToken authToken = new AuthToken();
        authToken.setAccessToken((String) responseEntityBody.get("access_token"));
        authToken.setRefreshToken((String) responseEntityBody.get("refresh_token"));
        authToken.setJti((String) responseEntityBody.get("jti"));

        stringRedisTemplate.boundValueOps(authToken.getJti()).set(authToken.getAccessToken(), ttl, TimeUnit.SECONDS);
        return authToken;
    }

    private String getHttpBasic(String clientId, String clientsecret) {
        String temp = clientId + ":" + clientsecret;
        byte[] bytes = Base64Utils.encode(temp.getBytes());
        return "Basic " + new String(bytes);
    }

}

3.Controller

/**
 * 认证控制层
 * @author ys
 */
@RestController
@RequestMapping("/oauth")
public class AuthController {

    @Autowired
    private AuthService authService;

    @Value("${auth.clientId}")
    private String clientId;

    @Value("${auth.clientSecret}")
    private String clientSecret;

    @Value("${auth.cookieDomain}")
    private String cookieDomain;

    @Value("${auth.cookieMaxAge}")
    private int cookieMaxAge;

    @PostMapping("/login")
    public Result login(@RequestParam("username") String username, @RequestParam("password") String password,
                        HttpServletResponse response, HttpServletRequest request) {
        if (StringUtils.isEmpty(username)) {
            throw new RuntimeException("用户不存在");
        }

        if (StringUtils.isEmpty(password)) {
            throw new RuntimeException("密码错误");
        }

        AuthToken token = authService.login(username, password, clientId, clientSecret);
        HttpServletResponse servletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        CookieUtil.addCookie(response, cookieDomain, "/", "uid", token.getJti(), cookieMaxAge, false);

        return new Result(true, StatusCode.OK, "获取Token成功", token.getJti());
    }
}

4.将登录请求放行,在WebSecurityConfig类中configure(),添加放行路径

 /***
     * 忽略安全拦截的URL
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/login",
                "/oauth/logout","/oauth/tologin","/login.html","/css/**","/data/**","/fonts/**","/img/**","/js/**");
    }
完成用户的授权

OAuth授权模式有四种:
1.授权码模式(Authorization Code)
2.隐式授权模式(Implicit)
3.密码模式(Resource Owner Password Credentials)
4.客户端模式(Client Credentials)

  • 新建一个需要授权的服务,添加OAuth的依赖
    在这里插入图片描述

  • 将public.key添加到resource目录下
    在这里插入图片描述

  • 编写配置类


@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    //公钥
    private static final String PUBLIC_KEY = "public.key";

    /***
     * 定义JwtTokenStore
     * @param jwtAccessTokenConverter
     * @return
     */
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    /***
     * 定义JJwtAccessTokenConverter
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }
    /**
     * 获取非对称加密公钥 Key
     * @return 公钥 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }

    /***
     * Http安全配置,对每个到达系统的http请求链接进行校验
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //所有请求必须认证通过
        http.authorizeRequests()
                //下边的路径放行
                .antMatchers(
                        "/user/add","/user/load/**"). //配置地址放行
                permitAll()
                .anyRequest().
                authenticated();    //其他地址需要认证授权
    }
}

访问user服务就需要携带Jwt令牌才可以,不然会出现下面的错误

"error": "unauthorized", 
"error_description": "Full authentication is required to access this resource" 
}

在http header中添加 Authorization: Bearer 令牌就可以访问了。

自定义项目跳转的登录页面
  • 修改配置类WebSecurityConfig添加登录页面的路径
@Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .httpBasic()        //启用Http基本身份验证
                .and()
                .formLogin()       //启用表单身份验证
                .and()
                .authorizeRequests()    //限制基于Request请求访问
                .anyRequest()
                .authenticated();       //其他请求都需要经过验证
        http.formLogin().loginPage("/oauth/tologin")
                .loginProcessingUrl("/oauth/login");
    }
  • 放行登录页面和相关的静态资源
/***
     * 忽略安全拦截的URL
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/login",
                "/oauth/logout","/oauth/tologin","/login.html","/css/**","/data/**","/fonts/**","/img/**","/js/**");
    }
  • 编写登录页面的跳转路径
login:function () {
				app.msg="正在登录";
				axios.post("/oauth/login?username="+app.username+"&password="+app.password).then(function (response) {
					if (response.data.flag){
						app.msg="登录成功";
					} else{
						app.msg="登录失败";
					}
				})
			}

有些代码和配置因为太繁琐我就没写到里面,以上就是本次分享,如果有不恰当的地方,欢迎指正。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 前后端分离是一种将前端界面与后端逻辑进行分离开发的架构方式,使得前端与后端可以并行开发。OAuth 2.0是一种授权框架,用于授权认证流程的规范化,而Spring Security是一个在Java实现安全控制的框架,提供了大量的安全特性。Spring Authorization Server是Spring Security中用于实现授权服务器的模块,它支持OAuth 2.0的各种授权模式。 密码模式是OAuth 2.0中的一种授权模式,它允许用户通过提交用户名和密码来获取访问令牌,然后使用该令牌来访问受保护的资源。在前后端分离的架构中,可以使用Spring Security配合Spring Authorization Server来实现密码模式的认证授权。 在密码模式下,前端首先需要收集用户用户名和密码,并将其发送给后端。后端使用Spring Security提供的密码编码器对密码进行加密,并验证用户名和密码的正确性。如果验证通过,则后端向客户端颁发一个访问令牌,通常是一个JWT(JSON Web Token)。前端使用获得的访问令牌来访问需要受保护的资源,每次请求将该令牌作为Authorization头的Bearer字段发送给后端进行验证。后端可以使用Spring Security的资源服务器来验证该令牌的有效性,并根据用户的权限控制对资源的访问。 使用Spring SecuritySpring Authorization Server的密码模式可以实现安全的前后端分离架构。通过合理配置和使用安全特性,可以保障用户的身份认证和资源的授权,确保系统的安全性。 ### 回答2: 前后端分离是一种软件架构模式,前端和后端通过使用API进行通信,分别负责处理用户界面和数据逻辑。OAuth 2.0是一种用于授权的开放标准协议,它允许用户在第三方应用程序中授权访问其受保护的资源。Spring SecuritySpring框架中的一个模块,提供了身份验证和授权功能。 在前后端分离的架构中,前端应用程序通常需要使用OAuth 2.0协议进行用户授权,以访问后端应用程序的受保护资源。为了实现密码模式,我们可以使用Spring Security的模块之一,即spring-authorization-server。 spring-authorization-server是Spring Security的一个子模块,用于实现OAuth 2.0协议中的授权服务器。密码模式是OAuth 2.0协议中的一种授权模式,允许前端应用程序通过用户用户名和密码进行授权。密码模式在安全性上有一定的风险,因此在实际应用中需要谨慎使用。 使用spring-authorization-server的密码模式,我们可以在前端应用程序中收集用户用户名和密码,并将其提交给后端应用程序进行验证。后端应用程序将使用Spring Security进行身份验证,并向前端应用程序颁发一个访问令牌,该令牌可以用于后续的API请求。 通过使用前后端分离、OAuth 2.0和spring-authorization-server的密码模式,我们可以实现安全的用户授权和身份验证机制,确保只有经过授权用户才能访问受保护的资源。这种架构模式能够提高系统的安全性和可扩展性,适用于各种类型的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值