什么是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基本上基于jdk1.8的环境,由于公司电脑的jdk是1.7,这里就简单的集成CAS就行,其他的认证方式等回家再写,家里的电脑jdk是1.8的。
- 项目结构
- pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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>org.ikane</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-pac4j-demo</name> <description>Spring-boot PAC4J DEMO</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.7</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> <version>2.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>spring-security-pac4j</artifactId> <version>1.4.1</version> <exclusions> <exclusion> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </exclusion> <exclusion> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-cas</artifactId> <version>1.8.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </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<GrantedAuthority> authorities; String password; public ClientUserDetails() { // TODO Auto-generated constructor stub } public ClientUserDetails(String username, String providerId, Collection<GrantedAuthority> authorities) { super(); this.username = username; this.providerId = providerId; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> 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<GrantedAuthority> 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<ClientAuthenticationToken> { 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<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); 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<Object,Object> 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
<!doctype html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Spring Pac4j Demo</title> </head> <body> <h2>Index Page sgdsfg</h2> </body> </html>
- login.html
<!doctype html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layouts/default"> <head> <title>Login</title> </head> <body> <div id="content" class="sign-in-page" layout:fragment="content"> <h2>Sign In</h2> <a th:href="${casAuthUrl}" th:class="'oauth-login-link cas-login'">CAS</a> <a th:href="${gitHubAuthUrl}" th:class="'oauth-login-link github-login'">GitHub</a> <a th:href="${google2AuthUrl}" th:class="'oauth-login-link google-login'">Google</a> <a th:href="${twitterAuthUrl}" th:class="'oauth-login-link twitter-login'">Twitter</a> </div> </body> </html>
- run
- index
本文转载自:https://blog.csdn.net/change_on/article/details/76302161