background

Most internal applications use the Central Authentication Service (CAS) to authenticate user login. If each application connects to domain account authentication separately, it will not only waste effort, but also cannot guarantee security. The common solution implements login authentication and Single Sign On (SSO) for the deployment of a CAS service. Instead of a bloated open source project solution, or building your own wheel, there is a lightweight solution -> Implementing CAS through GitLab’s Applications.

CAS

There are a lot of related materials on the Internet. Here we only describe the basic interaction process

node "APP" AS c
node "CAS Server" AS s
node "Web Browser" AS b

b -up-> c: 1. request
c -> s: 2. redirect login page
b <-up-> s: 3. login
s -> c: 4. ticket
c -> s: 5. valid tocket
s -> c: 6. user info
Copy the code

implementation

Gitlab is used as an authorization server to interact with Gitlab Applications through code and call apis to obtain user information.

Gitlab CAS

The CAS interaction process is basically followed, and some fields are named differently.

actor User as u
participant Gitlab as g
u -> app: request 
app -> app: valid session authorization
alt authorization login
    app -> u: view
else 
    app -> u: redirect login page
    u -> g: submit login form
    g -> app: redirect callbackUrl with authorization code 
    app -> g: get access token by authorization code
    g -> app: return access token
    app -> g: get user info
    g -> app: return user info
    g -> g: store user info with authorization key
    g -> u: set cookie authorization key
    u -> app: redirect referer url
    app -> app: valid session authorization
    app -> u: view
end

Copy the code

Code implementation

  1. clientId & SecretBy creating aGitlab applicationsTo obtain
  2. Through the browser, socallbackThe address can belocalhost
  3. User information can be stored or JWT
  4. The code is a sample code and can be encapsulated according to requirements in actual usestarter
@Controller
public class OauthController {

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

    @Value("${oauth2.server.url:https://gitlab.com}")
    private String gitlabServerUrl;
    @Value("${oauth2.client.id:xxx}")
    private String clientId;
    @Value("${oauth2.client.secret:xxxx}")
    private String clientSecret;
    @Value("${oauth2.client.callback.url:http://localhost:9000/callback}")
    private String callbackUrl;

    private static final String CURRENT_USER = "CurrentUser";
    private static final String AUTHORIZATION_KEY = "Authorization";
    private Map<String, User> userStore = new HashMap<>();
    private RestTemplate restTemplate = new RestTemplate();

    @GetMapping({"/main"."/"})
    @ResponseBody
    public String main(a) {
        User user = (User) RequestContextHolder.getRequestAttributes().getAttribute(CURRENT_USER, RequestAttributes.SCOPE_SESSION);
        return "<html><body>hi:" + user.username + " This is Main</body></html>";
    }

    /** * Authorization redirect URL *@paramCode is used to get accessToken and can only be used once */
    @GetMapping("/callback")
    public String callback(@RequestParam(value = "code", required = false) String code,
                           RedirectAttributes redirectAttributes,
                           HttpServletRequest request, HttpServletResponse response) {
        String referer = request.getParameter("referer");
        String accessToken = getAccessToken(code, buildCallbackUrl(referer));
        User user = getUser(accessToken);

        String uuid = UUID.randomUUID().toString();
        userStore.put(uuid, user);
        //set cookie
        response.addCookie(new Cookie(AUTHORIZATION_KEY, uuid));
        return "redirect:" + referer;
    }

    private String buildCallbackUrl(String referer) {
        return callbackUrl + "? referer=" + referer;
    }

    private User getUser(String accessToken) {
        return restTemplate.getForObject(gitlabServerUrl + "/api/v4/user? access_token=" + accessToken, User.class);
    }

    /** * Get accessToken * from gitlab via code@param code
     * @paramRedirectUri Callback address, which must be the same as the authorization parameter */
    private String getAccessToken(String code, String redirectUri) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type"."authorization_code");
        params.add("client_id", clientId);
        params.add("client_secret", clientSecret);
        params.add("code", code);
        params.add("redirect_uri", redirectUri);

        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);

        ResponseEntity<JSONAccessTokenResponse> response =
                restTemplate.exchange(gitlabServerUrl + "/oauth/token",
                        HttpMethod.POST,
                        entity,
                        JSONAccessTokenResponse.class);
        return Objects.requireNonNull(response.getBody()).access_token;
    }

    @Configuration
    class WebConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(
                    new HandlerInterceptorAdapter() {
                        @Override
                        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                            Optional<String> authorizationKeyOp = Arrays.stream(request.getCookies())
                                    .filter(it->it.getName().equals(AUTHORIZATION_KEY))
                                    .map(Cookie::getValue)
                                    .findAny();
                            if (authorizationKeyOp.isPresent()) {
                                // The authorization information exists, and the user information is added to the session
                                RequestContextHolder.getRequestAttributes().setAttribute(CURRENT_USER, userStore.get(authorizationKeyOp.get()), RequestAttributes.SCOPE_SESSION);
                                return super.preHandle(request, response, handler);
                            } else {
                                // The authorization information does not exist, go to gitlab for verification
                                String referer = request.getRequestURL().toString();
                                String redirectUri = URLEncoder.encode(buildCallbackUrl(referer), "utf-8");
                                String gitlabAuthUrl = gitlabServerUrl + "/oauth/authorize? response_type=code&redirect_uri=" + redirectUri + "&client_id=" + clientId;
                                logger.info("gitlabAuthUrl:{}", gitlabAuthUrl);
                                response.sendRedirect(gitlabAuthUrl);
                                return false;
                            }
                        }
                    })
                    .addPathPatterns("/main"."/test"); }}static class JSONAccessTokenResponse implements Serializable {
        public String access_token;
    }

    static class User implements Serializable {
        public String name;
        publicString username; }}Copy the code

New Gitlab applications

  1. Enter the name and Redirect URI in User Settings -> Applications, just check Confidential
  2. Click Save and record ClientId and Secret

The effect

  1. Browser to access http://localhost:9000/main
  2. Jump toGitlabThe login page
  3. Agree authorization (this action only needs to be done once)
  4. Automatic jump tohttp://localhost:9000/mainObtain the login account information
  5. Refresh the page to login state, no need to log in again. The login information is already inCookieYou do not need to log in again under the same domain name.
  6. inGitlab ApplicationsYou can view authorization status and cancel authorization

FAQs

  1. Gitlab errorThe redirect url included is not valid-> Confirm deliverycallback urlPath and parameters andapplicationsIs filled in the same