1.最近在研究前后端分离项目,领导安排任务:使用Oauth2实现登录认证。因为第一次接触vue对里面的结构方法使用情况等不是很了解走了很多弯路。现在记录使用Oauth2实现登录认证,供大家参考.

2.首先了解了下Oauth2认证方式。

    Oauth2分为四种认证方式:授权码认证(authorization-code),隐藏式(implicit),密码式(password),凭证式(client credentials)。因为是前后端分离项目,所以使用最常用的方式,安全性能高的授权码方式。

关于Oauth认证此处不做介绍。可访问链家了解详情。

此链接是妧一峰老师的文章,讲的很详细,很受用。

http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html#support

3.下面正式记录vue+springboot+oauth的登录认证实现代码。因为自己经常去网上搜索时代码都是片段,对于新手来说还是很伤脑筋,所以此处直接贴整个代码。

login.vue界面:

<template>
  <div class="login">
    <a-form @submit.prevent="doLogin" :autoFormCreate="(form) => this.form = form">
      <a-tabs size="large" :tabBarStyle="{textAlign: 'center'}" style="padding: 0 2px;" :activeKey="activeKey"
              @change="handleTabsChange">
        <a-tab-pane tab="账户密码登录" key="1">
          <a-alert type="error" :closable="true" v-show="error" :message="error" showIcon
                   style="margin-bottom: 24px;"></a-alert>
          <a-form-item
            fieldDecoratorId="name"
            :fieldDecoratorOptions="{rules: [{ required: true, message: '请输入账户名', whitespace: true}]}">
            <a-input size="large">
              <a-icon slot="prefix" type="user"></a-icon>
            </a-input>
          </a-form-item>
          <a-form-item
            fieldDecoratorId="password"
            :fieldDecoratorOptions="{rules: [{ required: true, message: '请输入密码', whitespace: true}]}">
            <a-input size="large" type="password">
              <a-icon slot="prefix" type="lock"></a-icon>
            </a-input>
          </a-form-item>
        </a-tab-pane>
      </a-tabs>
      <a-form-item>
        <a-button :loading="loading" style="width: 100%; margin-top: 4px" size="large" htmlType="submit" type="primary">
          登录
        </a-button>
      </a-form-item>
      <div>
        <a style="float: left" @click="authentication">oauth2认证登录</a>
        <a style="float: right" @click="regist">注册账户</a>
      </div>
    </a-form>
  </div>
</template>

<script>
import {mapMutations} from 'vuex'
import Qs from 'qs'

export default {
  name: 'Login',
  data () {
    return {
      loading: false,
      error: '',
      activeKey: '1',
      code: '',
      userAuthorizationUri: 'http://10.12.0.12:8080/oauth2/authorize', // 请求授权地址
      redirect_uri: 'http://localhost:8081/rapidoAdmin', // 回调地址
      client_id: '75cbff63-920e-448f-bd67-8b185d907b59', // 客户端相关标识,请从认证服务器申请
      client_secret: '119912f9-c015-49ae-a438-81e3edfc4de3',
      loginStyle: 'popup',
      state: 'rapido', // 可选参数,客户端的当前状态,可以指定任意值,用于校验,此次案例不做相关认证
      response_type: 'code', // 一些固定的请求参数
      grant_type: 'authorization_code'
    }
  },
  computed: {
    systemName () {
      return this.$store.state.setting.systemName
    },
    copyright () {
      return this.$store.state.setting.copyright
    }
  },
  created () {
    this.$db.clear()
    this.$router.options.routes = []
  },
  methods: {
    doLogin () {
      if (this.activeKey === '1') {
        // 用户名密码登录
        this.form.validateFields(['name', 'password'], (errors, values) => {
          if (!errors) {
            this.loading = true
            let name = this.form.getFieldValue('name')
            let password = this.form.getFieldValue('password')
            this.$post('login', {
              username: name,
              password: password
            }).then((r) => {
              let data = r.data.data
              this.saveLoginData(data)
              setTimeout(() => {
                this.loading = false
              }, 500)
              this.$router.push('/')
            }).catch(() => {
              setTimeout(() => {
                this.loading = false
              }, 500)
            })
          }
        })
      }
      if (this.activeKey === '2') {
        // 手机验证码登录
        this.$message.warning('暂未开发')
      }
    },
    regist () {
      this.$emit('regist', 'Regist')
    },
    authentication () {
      let authorUrl = this.userAuthorizationUri
      authorUrl = authorUrl + ('?' + Qs.stringify({
        loginStyle: this.loginStyle,
        client_id: this.client_id,
        response_type: this.response_type,
        scope: this.scope,
        redirect_uri: this.redirect_uri,
        state: this.state
      }))
      window.location.href = authorUrl
    },
    getCaptcha () {
      this.$message.warning('暂未开发')
    },
    handleTabsChange (val) {
      this.activeKey = val
    },
    ...mapMutations({
      setToken: 'account/setToken',
      setExpireTime: 'account/setExpireTime',
      setPermissions: 'account/setPermissions',
      setRoles: 'account/setRoles',
      setUser: 'account/setUser',
      setTheme: 'setting/setTheme',
      setLayout: 'setting/setLayout',
      setMultipage: 'setting/setMultipage',
      fixSiderbar: 'setting/fixSiderbar',
      fixHeader: 'setting/fixHeader',
      setColor: 'setting/setColor'
    }),
    saveLoginData (data) {
      this.setToken(data.token)
      this.setExpireTime(data.exipreTime)
      this.setUser(data.user)
      this.setPermissions(data.permissions)
      this.setRoles(data.roles)
      this.setTheme(data.config.theme)
      this.setLayout(data.config.layout)
      this.setMultipage(data.config.multiPage === '1')
      this.fixSiderbar(data.config.fixSiderbar === '1')
      this.fixHeader(data.config.fixHeader === '1')
      this.setColor(data.config.color)
      console.log(data.permissions)
      console.log(data.user.username)
      console.log(data.config.fixSiderbar)
      console.log(data.config.fixHeader)
    }
  },
  mounted () {
    let authCodeURL = window.location.href
    this.code = authCodeURL.substring(authCodeURL.indexOf('code'), authCodeURL.indexOf('&'))
    this.$post('authLogin', {
      code: this.code
    }).then((r) => {
      let data = r.data.data
      console.log('data:' + data)
      this.saveLoginData(data)
      this.loading = false
      this.$router.push('/')
    }).catch(() => {
      setTimeout(() => {
        this.loading = false
      }, 500)
    })
  }
}
</script>

<style lang="less" scoped>
  .login {
    .icon {
      font-size: 24px;
      color: rgba(0, 0, 0, 0.2);
      margin-left: 16px;
      vertical-align: middle;
      cursor: pointer;
      transition: color 0.3s;

      &:hover {
        color: #1890ff;
      }
    }
  }
</style>

4.前段拿到授权码去后台申请令牌获取资源

package com.ftsafe.rapido.system.controller;

import com.ftsafe.entity.RapidoOnlineUserEntity;
import com.ftsafe.rapido.common.annotation.Limit;
import com.ftsafe.rapido.common.authentication.JWTToken;
import com.ftsafe.rapido.common.authentication.JWTUtil;
import com.ftsafe.rapido.common.domain.ActiveUser;
import com.ftsafe.rapido.common.domain.RapidoConstant;
import com.ftsafe.rapido.common.domain.RapidoResponse;
import com.ftsafe.rapido.common.exception.RapidoException;
import com.ftsafe.rapido.common.properties.RapidoProperties;
import com.ftsafe.rapido.common.service.RedisService;
import com.ftsafe.rapido.common.utils.*;
import com.ftsafe.rapido.system.dao.LoginLogMapper;
import com.ftsafe.rapido.system.domain.LoginLog;
import com.ftsafe.rapido.system.domain.User;
import com.ftsafe.rapido.system.domain.UserConfig;
import com.ftsafe.rapido.system.manager.UserManager;
import com.ftsafe.rapido.system.service.LoginLogService;
import com.ftsafe.rapido.system.service.UserService;
import com.ftsafe.service.IRapidoBussinessService;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.lang3.StringUtils;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;

@Validated
@RestController
public class LoginController {

    @Autowired
    private RedisService redisService;
    @Autowired
    private UserManager userManager;
    @Autowired
    private UserService userService;
    @Autowired
    private LoginLogService loginLogService;
    @Autowired
    private LoginLogMapper loginLogMapper;
    @Autowired
    private RapidoProperties properties;
    @Autowired
    private ObjectMapper mapper;
    
    @Reference(version = "${rapido.service.version}",
            application = "${dubbo.application.id}",
            retries = -1,
            interfaceClass = IRapidoBussinessService.class)
    private IRapidoBussinessService rapidoBussinessService;

    @PostMapping("/authLogin")
    @Limit(key = "authLogin", period = 60, count = 20, name = "登录认证接口", prefix = "limit")
    public RapidoResponse authLogin(
            @NotBlank(message = "{required}") String code, HttpServletRequest request) throws Exception {
    	final String errorMessage = "获取登录认证错误";
    	String userInfoStr = "";
    	String token = "";
    	//令牌
    	code = code.substring(code.indexOf("=")+1);
    	//设置请求令牌请求求信息
    	OAuthClientRequest accessTokenRequest = OAuthClientRequest.
    			tokenLocation(RapidoConstant.OAUTH_CLIENT_ACCESS_TOKEN).
    			setGrantType(GrantType.AUTHORIZATION_CODE).
    			setClientId(RapidoConstant.OAUTH_CLIENT_ID).
    			setClientSecret(RapidoConstant.OAUTH_CLIENT_SECRET).
    			setCode(code).
    			setRedirectURI(RapidoConstant.OAUTH_CLIENT_CALLBACK).
    			buildQueryMessage();
    			
    	OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
    	OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
    	
    	//token令牌
    	token = oAuthResponse.getAccessToken();
    	Long expiresln = oAuthResponse.getExpiresIn();//过期时间
    	if(StringUtils.isEmpty(token)){
    		throw new RapidoException("获取令牌错误");
    	}
    	if(null == expiresln){
    		throw new RapidoException("令牌已过期或失效");
    	}
    	
    	//设置获取资源请求头信息
    	OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(RapidoConstant.OAUTH_CLIENT_USER_INFO).setAccessToken(token).buildQueryMessage();
    	userInfoRequest.setHeader("Authorization", "Bearer "+token);
    	
    	OAuthResourceResponse resourceResponse = oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
    		
    	userInfoStr = resourceResponse.getBody();
    	if(StringUtils.isEmpty(userInfoStr)){
    		throw new RapidoException("获取资源失败");
    	}
    	User user = JSONObject.parseObject(userInfoStr , User.class);// jsonStr 是String类型。
    	User auUser = this.userManager.getUser(user.getUsername());
    	auUser.setFullUserName(user.getFullUserName());
    	auUser.setEmail(user.getEmail());
    	  // 更新用户登录时间
        this.userService.updateLoginTime(auUser.getUsername());
        // 保存登录记录
        LoginLog loginLog = new LoginLog();
        loginLog.setUsername(user.getUsername());
        this.loginLogService.saveLoginLog(loginLog);
        
        //token有效期
    	Date du = new Date();
    	SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
    	long expiresa = du.getTime() + expiresln*1000;
    	
		//token加密
		token = RapidoUtil.encryptToken(JWTUtil.sign(user.getUsername(), "1de0f8a155afee2bf642bb57e38bc188"));
    	JWTToken tokenObj = new JWTToken(token,String.valueOf(df.format(expiresa)));
    	
    	//设置redis
		String userId = this.saveTokenToRedis(user, tokenObj, request);
		auUser.setId(userId);
	     
        Map<String, Object> userInfo = this.generateUserInfo(tokenObj, auUser);
    	return new RapidoResponse().message("认证成功").data(userInfo);
    }

}
Logo

前往低代码交流专区

更多推荐