Java ใช้ pac4j connect to Keycloak

| Java | 50 | 25 วันที่แล้ว
สวัสดีคับ บทความนี้ ผมจะเอาตัวอย่างโค้ด Java ที่ใช้ pac4j connect กับ Keycloak มาฝากกันคับ สำหรับใครที่ต้องการตัวอย่างหรือนำโค้ดไปดูกันเลย

ก่อนอื่นเราไปเพิ่ม pom.xml กันก่อน

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.howtoclicks.testcode</groupId>
    <artifactId>testconnect</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>testconnect</name>
    
    <repositories>
        <repository>
            <id>prime-repo</id>
            <name>Prime Repo</name>
            <url>http://repository.primefaces.org</url>
        </repository>
    </repositories>
    
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
        <jakartaee>8.0</jakartaee>
        
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>${jakartaee}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.core</groupId>
            <artifactId>arquillian-core-api</artifactId>
            <version>1.4.1.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <version>1.4.1.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.protocol</groupId>
            <artifactId>arquillian-protocol-servlet</artifactId>
            <version>1.4.1.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-transaction-jta</artifactId>
            <version>1.0.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>5.4.1.Final</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.jboss.logging</groupId>
                    <artifactId>jboss-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.primefaces.extensions</groupId>
            <artifactId>primefaces-extensions</artifactId>
            <version>8.0</version>
        </dependency>
        <dependency>
            <groupId>org.primefaces</groupId>
            <artifactId>primefaces</artifactId>
            <version>8.0</version>
        </dependency>
        <dependency>  
            <groupId>org.primefaces.themes</groupId>  
            <artifactId>all-themes</artifactId>  
            <version>1.0.10</version>  
        </dependency>
        <dependency>
            <groupId>org.pac4j</groupId>
            <artifactId>jee-pac4j</artifactId>
            <version>5.0.1-SNAPSHOT</version>
        </dependency>
        
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

        <dependency>
            <groupId>org.pac4j</groupId>
            <artifactId>pac4j-oidc</artifactId>
            <version>4.1.0</version>
        </dependency>

    </dependencies>
    
     <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-api</artifactId>
                                    <version>${jakartaee}</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
ต่อไปไฟล์หลัก ๆ จะมีอยู่ 2 ไฟล์นั่นคือ SecurityConfig กับ WebConfig เราไปดู SecurityConfig กันก่อน จะเป็นการกำหนดค่า config ต่าง ๆ ไปดูตัวอย่างโค้ดกัน
package com.howtoclicks.testcode;

import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import org.pac4j.core.config.Config;
import org.pac4j.oidc.client.KeycloakOidcClient;
import org.pac4j.oidc.config.KeycloakOidcConfiguration;

@ManagedBean
@ApplicationScoped
public class SecurityConfig {
    private static final Logger LOG = Logger.getLogger(SecurityConfig.class.getName());
    
    private Config config;
    
    @PostConstruct
    private void buildConfiguration() {
        LOG.info("building Security configuration...");
        
        KeycloakOidcConfiguration keyConfig = new KeycloakOidcConfiguration();
        keyConfig.setClientId("xxx");
        keyConfig.setSecret("xxx");
        keyConfig.setRealm("xxx");
        keyConfig.setBaseUri("http://test.myloyalty.howtoclicks.com/auth");

        KeycloakOidcClient oidcClient = new KeycloakOidcClient(keyConfig);
        oidcClient.setCallbackUrl("http://localhost:8080/tescode/callback");
        
        this.config = new Config(oidcClient);
    }
    
    public Config getConfig(){
        return this.config;
    }
}
จากโค้ดเราก็จะกำหนด config ต่าง ๆ และทำการบอกว่า ให้ callback ไปที่ไหน จากนั้นต่อไปเราดูไฟล์ WebConfig กันต่อเลย WebConfig จะเป็นการ config เกี่ยวกับเว็บของเราที่จะเอาไปทำการเชื่อมต่อ ซึ่งในตัวอย่างผมทำการ map path ที่ต้องการไว้ เช่น callback logout ต่าง ๆ ไปดูตัวอย่างโค้ดกันเลย
package com.howtoclicks.testcode;

import java.util.logging.Logger;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.ServletContext;
import org.pac4j.jee.filter.CallbackFilter;
import org.pac4j.jee.filter.LogoutFilter;
import org.pac4j.jee.filter.SecurityFilter;
import org.pac4j.jee.util.FilterHelper;

@Named
@ApplicationScoped
public class WebConfig {
    private static final Logger LOG = Logger.getLogger(WebConfig.class.getName());
    
    @Inject
    private SecurityConfig securityConfig;
    
    public void build(@Observes @Initialized(ApplicationScoped.class) ServletContext servletContext) {
        LOG.info("building Web configuration...");
        
        final FilterHelper filterHelper = new FilterHelper(servletContext);
        
        final CallbackFilter callbackFilter = new CallbackFilter(securityConfig.getConfig(), "http://localhost:8080/testcode/");
        callbackFilter.setRenewSession(true);
        filterHelper.addFilterMapping("callbackFilter", callbackFilter, "/callback");
        
        final SecurityFilter protectedFilter = new SecurityFilter(securityConfig.getConfig());
        filterHelper.addFilterMapping("protectedFilter", protectedFilter, "/*");
        
        final LogoutFilter logoutFilter = new LogoutFilter(securityConfig.getConfig(), "http://test.myloyalty.howtoclicks.com/auth/realms/TEST/protocol/openid-connect/logout?redirect_uri=http://localhost:8080/testcode/");
        logoutFilter.setDestroySession(true);
        filterHelper.addFilterMapping("logoutFilter", logoutFilter, "/logout");
    }
}
จากโค้ดผมจะทำการ map path ต่าง ๆ ซึ่ง protectedFilter จะทำการ เช็คว่ามีการ auth แล้วหรือเปล่า ผมใช้ /* เพื่อบอกว่า ให้เช็คทุก URL ส่วนใครจะเช็คแค่บาง URL ก็แก้ไขส่วนนี้ได้เลย จากนั้นเราไปสร้าง ProfileView เพื่อ get ค่า profile ที่ได้กลับคืนมากัน ตัวอย่างโค้ดตามด้านล่าง
package com.howtoclicks.testcode;

import java.util.List;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.profile.ProfileManager;

@Named
@RequestScoped
public class ProfileView {
    @Inject
    private WebContext webContext;

    @Inject
    private ProfileManager profileManager;

    public Object getProfile() {
        return profileManager.get(true).orElse(null);
    }
    
    public List getProfiles() {
        return profileManager.getAll(true);
    }
    
    public boolean isAuth(){
        return profileManager.isAuthenticated();
    }
}
จากนั้นลองสร้าง Index มาเพื่อ get ค่าดูเลย
<ui:composition template = "templates/common.xhtml">
    <ui:define name = "content">
        <h:body>
            <h:form enctype="multipart/form-data" id="form">
                <h1>
                    <h:outputLabel value="#{profileView.profile.email}" />
                </h1>
                
                name: <h:outputText value="#{profileView.profile.attributes.get('name')}"/>
                <br />
                email: <h:outputText value="#{profileView.profile.attributes.get('email')}"/>
                
                <br />
                roles: <h:outputLabel value="#{profileView.profile.roles}" />
                <br />
                permissions: <h:outputLabel value="#{profileView.profile.permissions}" />
                
                <br /><br />
                Manager: <h:outputLabel value="#{index.helloManager}" />
                <br />
                Sales: <h:outputLabel value="#{index.helloSales}" />
                
                <br /><br />
                <p:panel style="overflow:scroll;" header="Profile Object">
                    <h:outputLabel value="#{profileView.profile}" />
                </p:panel>
            </h:form>
        </h:body>
    </ui:define>
</ui:composition>
เพียงแค่นี้เราก็ได้ตัวอย่างโค้ดง่าย ๆ มาดูกันแล้ว ลองนำไปปรับใช้กันดูคับ
comments

[1]
AGI
58 D
[1]
Adobe-PDF
1162 D
[1]
Android
1053 D
[2]
Angular
36 D
[40]
Animal
1146 D
[1]
Apache
1162 D
[1]
[4]
[2]
[1]
[10]
CMS-Joomla
1163 D
[2]
CMS-SMF
1163 D
[1]
[4]
[1]
[3]
[1]
Database
1162 D
[4]
[1]
DeAI
23 D
[1]
DeData
23 D
[1]
DeFi
23 D
[2]
Docker
53 D
[2]
[1]
Forex
20 D
[1]
Fruit
1147 D
[1]
General
44 D
[2]
Git
949 D
[6]
HTML
53 D
[1]
Health
48 D
[1]
Housework
1151 D
[2]
IT
1143 D
[2]
Imacro
1163 D
[20]
Java
25 D
[1]
Java-Web
1053 D
[2]
[2]
MQL5
975 D
[3]
MakeMoney
948 D
[18]
[1]
[1]
Maven
947 D
[5]
[1]
Mobile
1160 D
[2]
NGINX
36 D
[2]
NodeJs
53 D
[1]
Oracle
53 D
[3]
Physics
944 D
[4]
PugJS
949 D
[2]
React
958 D
[132]
Science
1145 D
[2]
[5]
Spring
36 D
[12]
[7]
[2]
[1]
[4]
Ubuntu
1113 D
[1]
WebLogic
1144 D
[4]