若依集成钉钉扫码登录

博客 动态
0 155
羽尘
羽尘 2022-06-14 20:00:30
悬赏:0 积分 收藏

若依集成钉钉扫码登录

准备

钉钉文档地址:https://open.dingtalk.com/document/orgapp-server/scan-qr-code-to-log-on-to-third-party-websites

这个是历史版本的文档,最新版本的测试不稳定,经常出现系统繁忙。

按照钉钉文档做好前期准备,这里只说明若依框架代码的调整。

前端

页面

修改登陆页面src/views/login.vue,增加钉钉登录按钮

<el-form-item >    <el-button size="medium" type="primary"  @click.native.prevent="ddLogin">        <span>扫码登录</span>    </el-button></el-form-item>
    ddLogin() {      window.location.href = "https://oapi.dingtalk.com/connect/qrconnect?appid=your appid&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=your redirect_uri"    }

新建页面src/views/sso.vue

<script>export default {    data() {        return {            code: '',            state: ''        }    },    created() {        const { params, query } = this.$route        this.code = query.code        this.state = query.state        // 钉钉        this.$store.dispatch("SSO", { code: this.code, state: this.state }).then(() => {            this.$router.push({ path: this.redirect || "/" }).catch(() => { });        }).catch(() => {            this.$message.error("系统异常,请稍后再试!");        });    },    render: function (h) {        return h() // avoid warning message    }}</script>

修改路由文件src/router/index.js增加路由

// 公共路由export const constantRoutes = [  .....  {    path: '/sso',    component: () => import('@/views/sso'),    hidden: true  },   ..... ]

修改src/permission.js,设置白名单

const whiteList = ['/login', '/auth-redirect', '/bind', '/register','/sso']

接口

新建src/api/sso.js

import request from '@/utils/request'// 登录方法export function sso(code,state) {  const data = {    code,    state  }  return request({    url: '/sso',    headers: {      isToken: false    },    method: 'post',    data: data  })}

修改src/store/modules/user.js actions增加钉钉登录

//SSOSSO({ commit }, info) {     const code = info.code    const state = info.state    return new Promise((resolve, reject) => {        sso(code, state).then(res => {            console.log('user.js sso')            console.log(res)            setToken(res.token)            commit('SET_TOKEN', res.token)            resolve()        }).catch(error => {            reject(error)        })    })}

后端

因为我对项目目录结构进行了调整,这里就不说明放在那个包下,大家根据自己情况使用即可。

Controller

新建SSOController

@RestControllerpublic class SSOController {    @Resource    private ISsoService ssoService;    /**     * 登录方法     *     * @param ssoBody 登录信息     * @return 结果     */    @PostMapping("/sso")    public AjaxResult sso(@RequestBody SSOBody ssoBody) throws ApiException {        AjaxResult ajax = AjaxResult.success();        // 生成令牌        String token = ssoService.login(ssoBody.getCode(), ssoBody.getState(), ssoBody.getType());        ajax.put(Constants.TOKEN, token);        return ajax;    }}

新建SSOBody

/** * sso登录对象 */public class SSOBody {    /**     * 编码     */    private String code;    /**     * 状态码 可以用来判断是哪个系统     */    private String state;    /**     * 登录系统 后面可以换成枚举     */    private String type;    public String getCode() {        return code;    }    public void setCode(String code) {        this.code = code;    }    public String getState() {        return state;    }    public void setState(String state) {        this.state = state;    }    public String getType() {        return type;    }    public void setType(String type) {        this.type = type;    }}

Service

新建ISsoService

public interface ISsoService {    /**     * sso登录     * @param code 登录码     * @param state 状态码     * @param type 登录系统 后面可以换成枚举     * @return token     */    public String login(String code,String state,String type) throws ApiException;    /**     * 根据手机号获取用户     * @param phonenumber 手机号     * @return 用户     */    public UserDetails loadUserByPhonenumber(String phonenumber);}

新建SsoServiceImpl

登录方法对应钉钉接口文档:https://open.dingtalk.com/document/orgapp-server/use-dingtalk-account-to-log-on-to-third-party-websites

@Servicepublic class SsoServiceImpl implements ISsoService {    private static final Logger log = LoggerFactory.getLogger(SsoServiceImpl.class);    @Autowired    private ISysUserService userService;    @Autowired    private SysPermissionService permissionService;    @Resource    private AuthenticationManager authenticationManager;    @Resource    private TokenService tokenService;    /**     * sso登录     *     * @param code  登录码     * @param state 状态码     * @param type  登录系统 后面可以换成枚举     * @return token     */    @Override    public String login(String code, String state, String type) throws ApiException {        //if type = xxx ....如果多种验证方式        // 获取access_token        String access_token = AccessTokenUtil.getToken();        // 通过临时授权码获取授权用户的个人信息        DefaultDingTalkClient client2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");        OapiSnsGetuserinfoBycodeRequest reqBycodeRequest = new OapiSnsGetuserinfoBycodeRequest();        // 通过扫描二维码,跳转指定的redirect_uri后,向url中追加的code临时授权码        reqBycodeRequest.setTmpAuthCode(code);        OapiSnsGetuserinfoBycodeResponse response = client.execute(reqBycodeRequest, "yourAppKey", "yourAppSecret");        // 根据unionid获取userid        String unionid = bycodeResponse.getUserInfo().getUnionid();        DingTalkClient clientDingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");        OapiUserGetbyunionidRequest reqGetbyunionidRequest = new OapiUserGetbyunionidRequest();        reqGetbyunionidRequest.setUnionid(unionid);        OapiUserGetbyunionidResponse oapiUserGetbyunionidResponse = clientDingTalkClient.execute(reqGetbyunionidRequest, access_token);        // 根据userId获取用户信息        String userid = oapiUserGetbyunionidResponse.getResult().getUserid();        DingTalkClient clientDingTalkClient2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");        OapiV2UserGetRequest reqGetRequest = new OapiV2UserGetRequest();        reqGetRequest.setUserid(userid);        reqGetRequest.setLanguage("zh_CN");        OapiV2UserGetResponse rspGetResponse = clientDingTalkClient2.execute(reqGetRequest, access_token);        // 用户验证        Authentication authentication = null;        authentication = authenticationManager.authenticate(new DingDingAuthenticationToken(rspGetResponse.getResult().getMobile()));        LoginUser loginUser = (LoginUser) authentication.getPrincipal();        AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.dd.login.success")));        recordLoginInfo(loginUser.getUserId());        // 生成token        return tokenService.createToken(loginUser);    }    /**     * 根据手机号获取用户     *     * @param phonenumber 手机号     * @return 用户     */    @Override    public UserDetails loadUserByPhonenumber(String phonenumber) {        {            SysUser user = userService.selectUserByPhonenumber(phonenumber);            if (StringUtils.isNull(user)) {                log.info("sso登录用户:{} 不存在.", phonenumber);                throw new ServiceException("登录用户不存在");            }            return createLoginUser(user);        }    }    public UserDetails createLoginUser(SysUser user) {        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));    }    /**     * 记录登录信息     *     * @param userId 用户ID     */    public void recordLoginInfo(Long userId) {        SysUser sysUser = new SysUser();        sysUser.setUserId(userId);        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));        sysUser.setLoginDate(DateUtils.getNowDate());        userService.updateUserProfile(sysUser);    }}

修改用户登录

修改ISysUserService增加通过手机号搜索用户方法

    /**     * 通过手机号查询用户     *     * @param phonenumber 用户名     * @return 用户对象信息     */    public SysUser selectUserByPhonenumber(String phonenumber);

实现

    /**     * 通过手机号查询用户     *     * @param phonenumber 用户名     * @return 用户对象信息     */    @Override    public SysUser selectUserByPhonenumber(String phonenumber) {        return userMapper.selectUserByPhonenumber(phonenumber);    }

Mapper

    /**     * 通过手机号查询用户     *     * @param phonenumber 用户名     * @return 用户对象信息     */    public SysUser selectUserByPhonenumber(String phonenumber);
	<select id="selectUserByPhonenumber" parameterType="String"  resultMap="SysUserResult">		<include refid="selectUserVo"/>		where u.phonenumber = #{phonenumber}	</select>

重点Spring Security

这里参考文章为:https://blog.csdn.net/dnf9906/article/details/113571941

SecurityConfig配置(framework/config/SecurityConfig.java)

/** * spring security配置 *  * @author jelly */@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter{    /**     * 自定义用户认证逻辑     */    @Autowired    private UserDetailsService userDetailsService;    /** 钉钉 认证器*/    @Autowired    private DingDingAuthenticationProvider dingDingAuthenticationProvider;        /**     * 认证失败处理类     */    @Autowired    private AuthenticationEntryPointImpl unauthorizedHandler;    /**     * 退出处理类     */    @Autowired    private LogoutSuccessHandlerImpl logoutSuccessHandler;    /**     * token认证过滤器     */    @Autowired    private JwtAuthenticationTokenFilter authenticationTokenFilter;        /**     * 跨域过滤器     */    @Autowired    private CorsFilter corsFilter;        /**     * 解决 无法直接注入 AuthenticationManager     *     * @return     * @throws Exception     */    @Bean    @Override    public AuthenticationManager authenticationManagerBean() throws Exception    {        return super.authenticationManagerBean();    }    /**     * anyRequest          |   匹配所有请求路径     * access              |   SpringEl表达式结果为true时可以访问     * anonymous           |   匿名可以访问     * denyAll             |   用户不能访问     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问     * permitAll           |   用户可以任意访问     * rememberMe          |   允许通过remember-me登录的用户访问     * authenticated       |   用户登录后可访问     */    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception    {        httpSecurity                // CSRF禁用,因为不使用session                .csrf().disable()                // 认证失败处理类                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()                // 基于token,所以不需要session                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()                // 过滤请求                .authorizeRequests()                // 使用 permitAll() 方法所有人都能访问,包括带上 token 访问                // 使用 anonymous() 所有人都能访问,但是带上 token 访问后会报错                // 对于登录login 注册register 验证码captchaImage 允许匿名访问                .antMatchers("/login", "/register", "/captchaImage").anonymous()                .antMatchers("/sso").anonymous()                .antMatchers(                        HttpMethod.GET,                        "/",                        "/*.html",                        "/**/*.html",                        "/**/*.css",                        "/**/*.js",                        "/profile/**"                ).permitAll()                .antMatchers("/swagger-ui.html").anonymous()                .antMatchers("/swagger-resources/**").anonymous()                .antMatchers("/webjars/**").anonymous()                .antMatchers("/*/api-docs").anonymous()                .antMatchers("/druid/**").anonymous()                // 除上面外的所有请求全部需要鉴权认证                .anyRequest().authenticated()                .and()                .headers().frameOptions().disable();        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);        // 添加JWT filter        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);        // 添加CORS filter        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);    }    /**     * 强散列哈希加密实现     */    @Bean    public BCryptPasswordEncoder bCryptPasswordEncoder()    {        return new BCryptPasswordEncoder();    }    /**     * 身份认证接口     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception    {        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());        // 新增 钉钉        auth.authenticationProvider(dingDingAuthenticationProvider);    }}

主要修改了两个地方

.antMatchers("/sso").anonymous()
  // 新增 钉钉auth.authenticationProvider(dingDingAuthenticationProvider);

新建Provider

新建DingDingAuthenticationProvider

/** * 钉钉登录 */@Componentpublic class DingDingAuthenticationProvider implements AuthenticationProvider {    /** 钉钉登录验证服务 */    @Autowired    private ISsoService ssoService;    /**     * 进行认证     *     * @param authentication     * @return     * @throws AuthenticationException     */    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        long time = System.currentTimeMillis();        System.out.println("钉钉登录验证");        String phone = authentication.getName();        // String rawCode = authentication.getCredentials().toString();        // 1.根据手机号获取用户信息        UserDetails userDetails = ssoService.loadUserByPhonenumber(phone);        if (Objects.isNull(userDetails)) {            throw new BadCredentialsException("钉钉当前用户未关联到系统用户");        }        // 3、返回经过认证的Authentication        DingDingAuthenticationToken result = new DingDingAuthenticationToken(userDetails, Collections.emptyList());        result.setDetails(authentication.getDetails());        System.out.println("钉钉登录验证完成");        return result;    }    @Override    public boolean supports(Class<?> authentication) {        boolean res = DingDingAuthenticationToken.class.isAssignableFrom(authentication);        System.out.println("钉钉进行登录验证 res:"+ res);        return res;    }}

新建DingDingAuthenticationToken

public class DingDingAuthenticationToken extends AbstractAuthenticationToken {    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;    // 手机号    private final Object principal;    /**     * 此构造函数用来初始化未授信凭据.     *     * @param principal     */    public DingDingAuthenticationToken(Object principal) {        super(null);        System.out.println("DingDingAuthenticationToken1"+principal.toString());        this.principal = principal;        setAuthenticated(false);    }    /**     * 此构造函数用来初始化授信凭据.     *     * @param principal     */    public DingDingAuthenticationToken(Object principal,Collection<? extends GrantedAuthority> authorities) {        super(authorities);        System.out.println("DingDingAuthenticationToken2"+principal.toString());        this.principal = principal;        super.setAuthenticated(true);    }    @Override    public Object getCredentials() {        return null;    }    @Override    public Object getPrincipal() {        return this.principal;    }    @Override    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {        if (isAuthenticated) {            throw new IllegalArgumentException(                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");        }        super.setAuthenticated(false);    }    @Override    public void eraseCredentials() {        super.eraseCredentials();    }}

测试

数据库给某个用户赋值钉钉手机号,扫码登录。

钉钉扫码登录

posted @ 2022-06-14 19:25 果冻栋吖 阅读(22) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员