PAC4J 初探


PAC4J 初探_第1张图片



什么是PAC4J?

pac4j是一个简单而强大的安全引擎,用于Java对用户进行身份验证、获取其配置文件和管理授权,以确保web应用程序安全。它提供了一套完整的概念和组件。它基于Java 8,并在Apache 2许可下使用。它可用于大多数框架/工具和支持大多数认证/授权机制。


已经集成可用的场景

  • J2E • Spring Web MVC (Spring Boot) • Spring Security (Spring Boot) • Apache Shiro

  • Play 2.x • Vertx • Spark Java • Ratpack • Undertow

  • CAS server • JAX-RS • Dropwizard • Apache Knox • Jooby


身份验证机制

  • OAuth (Facebook, Twitter, Google…) - SAML - CAS - OpenID Connect - HTTP - OpenID - Google App Engine - Kerberos (SPNEGO/Negotiate)

  • LDAP - SQL - JWT - MongoDB - CouchDB - IP address - REST API


授权机制

  • Roles/permissions - Anonymous/remember-me/(fully) authenticated - Profile type, attribute

  • CORS - CSRF - Security headers - IP address, HTTP method



PAC4J 初探_第2张图片



PAC4J基本上基于jdk1.8的环境,由于公司电脑的jdk是1.7,这里就简单的集成CAS就行,其他的认证方式等回家再写,家里的电脑jdk是1.8的。


  • 项目结构


PAC4J 初探_第3张图片


  • 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.0modelVersion>

    <groupId>org.ikanegroupId>
    <artifactId>demoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>jarpackaging>

    <name>spring-boot-pac4j-demoname>
    <description>Spring-boot PAC4J DEMOdescription>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>1.2.8.RELEASEversion>
        <relativePath/> 
    parent>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <java.version>1.7java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.thymeleaf.extrasgroupId>
            <artifactId>thymeleaf-extras-springsecurity4artifactId>
            <version>2.1.2.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>

        <dependency>
            <groupId>org.pac4jgroupId>
            <artifactId>spring-security-pac4jartifactId>
            <version>1.4.1version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.securitygroupId>
                    <artifactId>spring-security-webartifactId>
                exclusion>
                <exclusion>
                    <groupId>org.springframework.securitygroupId>
                    <artifactId>spring-security-configartifactId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.pac4jgroupId>
            <artifactId>pac4j-casartifactId>
            <version>1.8.5version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>


project>


  • Pac4jConfig.java
package org.ikane;

import org.ikane.security.ClientUserDetailsService;
import org.ikane.service.AccountService;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.client.Clients;
import org.pac4j.springframework.security.authentication.ClientAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class Pac4jConfig {

    public static String CAS_LOGIN_URL = "https://casserverpac4j.herokuapp.com/login";

    @Value("${oauth.callback.url}")
    private String oauthCallbackUrl;

    @Autowired
    AccountService accountService;

    @Bean
    CasClient casClient() {
        return new CasClient(CAS_LOGIN_URL);
    }

    @Bean
    Clients clients() {
        return new Clients(oauthCallbackUrl, casClient());
    }



    @Bean
    ClientUserDetailsService clientUserDetailsService() {
        ClientUserDetailsService clientUserDetailsService = new ClientUserDetailsService();
        clientUserDetailsService.setAccountService(accountService);
        return clientUserDetailsService;
    }

    @Bean
    ClientAuthenticationProvider clientProvider() {
        ClientAuthenticationProvider clientAuthenticationProvider = new ClientAuthenticationProvider();
        clientAuthenticationProvider.setClients(clients());
        clientAuthenticationProvider.setUserDetailsService(clientUserDetailsService());
        return clientAuthenticationProvider;
    }

}


  • SecurityConfig.java
package org.ikane;

import org.pac4j.core.client.Clients;
import org.pac4j.springframework.security.authentication.ClientAuthenticationProvider;
import org.pac4j.springframework.security.web.ClientAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    ApplicationContext context;

    @Autowired
    Clients clients;

    @Autowired
    ClientAuthenticationProvider clientProvider;

    @Override
    public void configure(WebSecurity web) throws Exception {
         web
         .ignoring()
         .antMatchers(
             "/**/*.css",
             "/**/*.png",
             "/**/*.gif",
             "/**/*.jpg",
             "/**/*.ico",
             "/**/*.js"
         );
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .csrf().disable()
        .authorizeRequests()
            .and()
        .formLogin()
            .loginPage("/login")
            .permitAll()
            .and()
        .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/")
            .permitAll()
         ;

        http.addFilterBefore(clientFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);

        auth.authenticationProvider(clientProvider);
    }

    ClientAuthenticationFilter clientFilter() {

        String suffixUrl="/*";

        ClientAuthenticationFilter clientAuthenticationFilter = new ClientAuthenticationFilter(suffixUrl);
        clientAuthenticationFilter.setClients(clients);
        clientAuthenticationFilter.setSessionAuthenticationStrategy(sas());
        //clientAuthenticationFilter.setAuthenticationManager((AuthenticationManager)clientProvider);

        return clientAuthenticationFilter;
        /*
        return new ClientAuthenticationFilter(
                clients: clients,
                sessionAuthenticationStrategy: sas(),
                authenticationManager: clientProvider as AuthenticationManager
        )
        */
    }

    @Bean
    SessionAuthenticationStrategy sas() {
        return new SessionFixationProtectionStrategy();
    }

}


  • SpringBootPac4jDemoApplication.java
package org.ikane;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootPac4jDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootPac4jDemoApplication.class, args);
    }
}


  • ThymeleafConfig.java
package org.ikane;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;

@Configuration
public class ThymeleafConfig {

    @Bean
    public SpringSecurityDialect springSecurityDialect() {
        return new SpringSecurityDialect();
    }
}


  • IndexController.java
package org.ikane.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
class IndexController {

    @RequestMapping("/")
    @PreAuthorize("isAuthenticated()")
    public String index() {
        return "index";
    }

}


  • LoginController.java
package org.ikane.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.cas.client.CasClient;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.client.Clients;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.RequiresHttpAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private Clients clients;

    @RequestMapping("/login")
    public String login(HttpServletRequest request, HttpServletResponse response, Model model) {

        if (isAuthenticated()) {
            return "redirect:/";
        }
        final WebContext context = new J2EContext(request, response);
        //定义cas客户端
        final CasClient casClient = (CasClient) clients.findClient(CasClient.class);
        model.addAttribute("casAuthUrl",  getClientLocation(casClient, context));
        return "login";
    }

    //获取客户端的链接
    public String getClientLocation(BaseClient client, WebContext context) {

        try {
            return ((CasClient)client).getRedirectAction(context, false).getLocation();
        } catch (RequiresHttpAction e) {
            e.printStackTrace();
            logger.error("error", e);
            return null;
        }

        //return client.getRedirectAction(context, false, false).getLocation();
    }


    protected boolean isAuthenticated() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return !(auth instanceof AnonymousAuthenticationToken);
    }
}


  • ClientUserDetails.java
package org.ikane.security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

class ClientUserDetails implements UserDetails {

    private static final long serialVersionUID = 6523314653561682296L;

    String username;
    String providerId;
    Collection authorities;
    String password;

    public ClientUserDetails() {
        // TODO Auto-generated constructor stub
    }

    public ClientUserDetails(String username, String providerId, Collection authorities) {
        super();
        this.username = username;
        this.providerId = providerId;
        this.authorities = authorities;
    }


    @Override
    public Collection getAuthorities() {
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return false;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
    public String getProviderId() {
        return providerId;
    }
    public void setProviderId(String providerId) {
        this.providerId = providerId;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void setAuthorities(Collection authorities) {
        this.authorities = authorities;
    }
    public void setPassword(String password) {
        this.password = password;
    }



}


  • ClientUserDetailsService.java
package org.ikane.security;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.ikane.service.AccountService;
import org.pac4j.springframework.security.authentication.ClientAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class ClientUserDetailsService implements AuthenticationUserDetailsService {

    private AccountService accountService;

    public UserDetails loadUserDetails(final ClientAuthenticationToken token) throws UsernameNotFoundException {

        Map account = accountService.lookupAccountByProvider(token.getClientName(), token.getUserProfile().getId());

        //String username = account.containsKey("displayName") ? account.displayName : ""
        String username = "admin";

        final List authorities = new ArrayList();
        for (String role: token.getUserProfile().getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role));
        }

        if (!account.isEmpty() && authorities.isEmpty()) {
            // default to user role
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        }

        return new ClientUserDetails(username, token.getUserProfile().getId(), authorities);
    }

    public AccountService getAccountService() {
        return accountService;
    }

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

}


  • AccountService.java
package org.ikane.service;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Service;

@Service
public class AccountService {


    /**
     * @Autowired
    JdbcTemplate jdbcTemplate
     * */

    public Map lookupAccountByProvider(String providerName, String providerUserId) {
        HashMap map = new HashMap<>();

        /**
         *  
         List results = jdbcTemplate.query(
                "select * from account where provider = ? and provider_user_id = ?",
                [providerName, providerUserId] as Object[],
                new GenericRowMapper()
        )

        if (results.size() > 1) {
            throw new Exception("multiple accounts by provider [${providerName}] for id [${providerUserId}]")
        }

         * **/

        return map;
    }

    public Boolean createAccountForProvider(String providerName, String providerUserId, String displayName) {

        /**
         *  log.debug("creating new account for displayName=${displayName} using provider=${providerName} with id ${providerUserId}")

        int result = jdbcTemplate.update(
                "insert into account (display_name, provider, provider_user_id) values (?, ?, ?)",
                displayName,
                providerName,
                providerUserId
        )

        if (result != 1) {
            log.warn("creation of account for provider [${providerName}] and id [${providerUserId}] failed")
            return false
        }
         * */
        return true;
    }
}


  • index.html

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Spring Pac4j Demotitle>
    head>
    <body>
        <h2>Index Page sgdsfgh2>
    body>
html>


  • login.html

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layouts/default">
    <head>
        <title>Logintitle>
    head>
    <body>
        <div id="content" class="sign-in-page" layout:fragment="content">
            <h2>Sign Inh2>
            <a th:href="${casAuthUrl}" th:class="'oauth-login-link cas-login'">CASa>
            <a th:href="${gitHubAuthUrl}" th:class="'oauth-login-link github-login'">GitHuba>
            <a th:href="${google2AuthUrl}" th:class="'oauth-login-link google-login'">Googlea>
            <a th:href="${twitterAuthUrl}" th:class="'oauth-login-link twitter-login'">Twittera>
        div>
    body>
html>


  • run


PAC4J 初探_第4张图片


  • index


PAC4J 初探_第5张图片



如果要完整源码请加我qq,个人主页那里有

你可能感兴趣的:(java,spring)