掙扎了兩週,Spring security的cas終於搞出來了,廢話很少說,開篇!javascript
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 6 <groupId>com.cas.client1</groupId> 7 <artifactId>cas-client1</artifactId> 8 <version>0.0.1-SNAPSHOT</version> 9 <packaging>jar</packaging> 10 11 <name>cas-client1</name> 12 <description>Demo project for Spring Boot</description> 13 14 <parent> 15 <groupId>org.springframework.boot</groupId> 16 <artifactId>spring-boot-starter-parent</artifactId> 17 <version>2.0.4.RELEASE</version> 18 <relativePath/> <!-- lookup parent from repository --> 19 </parent> 20 21 <properties> 22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 23 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 24 <java.version>1.8</java.version> 25 </properties> 26 27 <dependencies> 28 <dependency> 29 <groupId>org.springframework.boot</groupId> 30 <artifactId>spring-boot-starter-web</artifactId> 31 </dependency> 32 <dependency> 33 <groupId>org.springframework.boot</groupId> 34 <artifactId>spring-boot-starter-thymeleaf</artifactId> 35 </dependency> 36 37 <dependency> 38 <groupId>org.springframework.boot</groupId> 39 <artifactId>spring-boot-starter-tomcat</artifactId> 40 <scope>provided</scope> 41 </dependency> 42 <dependency> 43 <groupId>junit</groupId> 44 <artifactId>junit</artifactId> 45 <version>4.12</version> 46 <scope>test</scope> 47 </dependency> 48 <dependency> 49 <groupId>org.springframework.boot</groupId> 50 <artifactId>spring-boot-starter-test</artifactId> 51 <scope>test</scope> 52 </dependency> 53 <dependency> 54 <groupId>org.springframework.boot</groupId> 55 <artifactId>spring-boot-starter-security</artifactId> 56 </dependency> 57 <dependency> 58 <groupId>org.springframework.security</groupId> 59 <artifactId>spring-security-test</artifactId> 60 <scope>test</scope> 61 </dependency> 62 <!-- security taglibs --> 63 <dependency> 64 <groupId>org.springframework.security</groupId> 65 <artifactId>spring-security-taglibs</artifactId> 66 </dependency> 67 <dependency> 68 <groupId>org.springframework.security.oauth</groupId> 69 <artifactId>spring-security-oauth2</artifactId> 70 <version>RELEASE</version> 71 </dependency> 72 <dependency> 73 <groupId>org.springframework.boot</groupId> 74 <artifactId>spring-boot-starter-data-jpa</artifactId> 75 </dependency> 76 <dependency> 77 <groupId>org.springframework.boot</groupId> 78 <artifactId>spring-boot-starter-jdbc</artifactId> 79 </dependency> 80 <dependency> 81 <groupId>mysql</groupId> 82 <artifactId>mysql-connector-java</artifactId> 83 <version>5.1.46</version> 84 </dependency> 85 <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> 86 <dependency> 87 <groupId>com.alibaba</groupId> 88 <artifactId>druid-spring-boot-starter</artifactId> 89 <version>1.1.10</version> 90 </dependency> 91 <dependency> 92 <groupId>org.springframework.boot</groupId> 93 <artifactId>spring-boot</artifactId> 94 <version>2.0.2.RELEASE</version> 95 <scope>compile</scope> 96 </dependency> 97 </dependencies> 98 99 <build> 100 <plugins> 101 <plugin> 102 <groupId>org.springframework.boot</groupId> 103 <artifactId>spring-boot-maven-plugin</artifactId> 104 </plugin> 105 </plugins> 106 </build> 107 108 109 </project>
application.properties:
css
1 server.port=8083 2 #靜態文件訪問存放地址 3 spring.resources.static-locations=classpath:/html/ 4 # thymeleaf 模板存放地址 5 spring.thymeleaf.prefix=classpath:/html/ 6 spring.thymeleaf.suffix=.html 7 spring.thymeleaf.mode=LEGACYHTML5 8 spring.thymeleaf.encoding=UTF-8 9 10 # JDBC 配置(驅動類自動從url的mysql識別,數據源類型自動識別) 11 # 或spring.datasource.url= 12 spring.datasource.druid.url=jdbc:mysql://localhost:3306/vhr?useUnicode=true&characterEncoding=UTF8 13 # 或spring.datasource.username= 14 spring.datasource.druid.username=root 15 # 或spring.datasource.password= 16 spring.datasource.druid.password=1234 17 #或 spring.datasource.driver-class-name= 18 #spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver 19 20 #鏈接池配置(一般來講,只須要修改initialSize、minIdle、maxActive 21 # 若是用Oracle,則把poolPreparedStatements配置爲true,mysql能夠配置爲false。分庫分表較多的數據庫,建議配置爲false。removeabandoned不建議在生產環境中打開若是用SQL Server,建議追加配置) 22 spring.datasource.druid.initial-size=1 23 spring.datasource.druid.max-active=20 24 spring.datasource.druid.min-idle=1 25 # 配置獲取鏈接等待超時的時間 26 spring.datasource.druid.max-wait=60000 27 #打開PSCache,而且指定每一個鏈接上PSCache的大小 28 spring.datasource.druid.pool-prepared-statements=true 29 spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20 30 #spring.datasource.druid.max-open-prepared-statements=和上面的等價 31 spring.datasource.druid.validation-query=SELECT 'x' 32 #spring.datasource.druid.validation-query-timeout= 33 spring.datasource.druid.test-on-borrow=false 34 spring.datasource.druid.test-on-return=false 35 spring.datasource.druid.test-while-idle=true 36 #配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 37 spring.datasource.druid.time-between-eviction-runs-millis=60000 38 #配置一個鏈接在池中最小生存的時間,單位是毫秒 39 spring.datasource.druid.min-evictable-idle-time-millis=300000 40 #spring.datasource.druid.max-evictable-idle-time-millis= 41 #配置多個英文逗號分隔 42 #spring.datasource.druid.filters= stat 43 44 # WebStatFilter配置,說明請參考Druid Wiki,配置_配置WebStatFilter 45 #是否啓用StatFilter默認值true 46 spring.datasource.druid.web-stat-filter.enabled=true 47 spring.datasource.druid.web-stat-filter.url-pattern=/* 48 spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/* 49 spring.datasource.druid.web-stat-filter.session-stat-enable=false 50 spring.datasource.druid.web-stat-filter.session-stat-max-count=1000 51 spring.datasource.druid.web-stat-filter.principal-session-name=admin 52 spring.datasource.druid.web-stat-filter.principal-cookie-name=admin 53 spring.datasource.druid.web-stat-filter.profile-enable=true 54 55 # StatViewServlet配置 56 #展現Druid的統計信息,StatViewServlet的用途包括:1.提供監控信息展現的html頁面2.提供監控信息的JSON API 57 #是否啓用StatViewServlet默認值true 58 spring.datasource.druid.stat-view-servlet.enabled=true 59 spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* 60 61 62 # JPA config 63 spring.jpa.database=mysql 64 spring.jpa.hibernate.ddl-auto=update 65 spring.jpa.show-sql=true 66 spring.jpa.generate-ddl=true 67 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 68 spring.jpa.open-in-view=true 69 # 解決jpa no session的問題 70 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
這裏使用數據庫存儲角色權限信息,分三種實體:用戶;角色;資源;用戶對角色多對多;角色對資源多對多
建立幾個實體類:
用戶:這裏直接使用用戶持久化對象實現Spring security要求的UserDetails接口,並實現對應方法html
1 package com.cas.client1.entity; 2 3 import org.springframework.security.core.GrantedAuthority; 4 import org.springframework.security.core.userdetails.UserDetails; 5 import org.springframework.util.CollectionUtils; 6 7 import javax.persistence.*; 8 import java.util.ArrayList; 9 import java.util.Collection; 10 import java.util.List; 11 12 @Entity 13 @Table(name = "s_user") 14 public class User implements UserDetails { 15 @Id 16 private String id; 17 @Column(name = "username") 18 private String username; 19 @Column(name = "password") 20 private String password; 21 22 @ManyToMany(fetch = FetchType.LAZY) 23 @JoinTable( 24 name = "s_user_role", 25 joinColumns = @JoinColumn(name = "user_id"), 26 inverseJoinColumns = @JoinColumn(name = "role_id") 27 ) 28 private List<Role> roles; 29 30 public User() { 31 } 32 33 public User(String id, String username, String password) { 34 this.id = id; 35 this.username = username; 36 this.password = password; 37 } 38 39 public String getId() { 40 return id; 41 } 42 43 public void setId(String id) { 44 this.id = id; 45 } 46 47 public List<Role> getRoles() { 48 return roles; 49 } 50 51 public void setRoles(List<Role> roles) { 52 this.roles = roles; 53 } 54 55 @Override 56 public String getUsername() { 57 return username; 58 } 59 60 @Override 61 public boolean isAccountNonExpired() { 62 return true; 63 } 64 65 @Override 66 public boolean isAccountNonLocked() { 67 return true; 68 } 69 70 @Override 71 public boolean isCredentialsNonExpired() { 72 return true; 73 } 74 75 @Override 76 public boolean isEnabled() { 77 return true; 78 } 79 80 public void setUsername(String username) { 81 this.username = username; 82 } 83 84 @Transient 85 List<GrantedAuthority> grantedAuthorities=new ArrayList<>(); 86 @Override 87 public Collection<? extends GrantedAuthority> getAuthorities() { 88 if (grantedAuthorities.size()==0){ 89 if (!CollectionUtils.isEmpty(roles)){ 90 for (Role role:roles){ 91 List<Resource> resources = role.getResources(); 92 if (!CollectionUtils.isEmpty(resources)){ 93 for (Resource resource:resources){ 94 grantedAuthorities.add(new SimpleGrantedAuthority(resource.getResCode())); 95 } 96 } 97 } 98 } 99 grantedAuthorities.add(new SimpleGrantedAuthority("AUTH_0")); 100 } 101 return grantedAuthorities; 102 } 103 @Override 104 public String getPassword() { 105 return password; 106 } 107 108 public void setPassword(String password) { 109 this.password = password; 110 } 111 }
注意看這裏:
java
我給每一位登陸的用戶都授予了AUTH_0的權限,AUTH_0在下面的SecurityMetaDataSource裏被關聯的url爲:/**,也就是說除開那些機密程度更高的,這個登陸用戶能訪問全部資源mysql
角色:
git
1 package com.cas.client1.entity; 2 3 import javax.persistence.*; 4 import java.util.List; 5 6 /** 7 * @author Administrator 8 */ 9 @Entity 10 @Table(name = "s_role") 11 public class Role { 12 @Id 13 @Column(name = "id") 14 private String id; 15 @Column(name = "role_name") 16 private String roleName; 17 18 @ManyToMany(fetch = FetchType.LAZY) 19 @JoinTable( 20 name = "s_role_res", 21 joinColumns = @JoinColumn(name = "role_id"), 22 inverseJoinColumns = @JoinColumn(name = "res_id") 23 ) 24 private List<Resource> resources; 25 @ManyToMany(fetch = FetchType.LAZY) 26 @JoinTable( 27 name = "s_user_role", 28 joinColumns = @JoinColumn(name = "role_id"), 29 inverseJoinColumns = @JoinColumn(name = "user_id") 30 ) 31 private List<User> users; 32 33 public String getId() { 34 return id; 35 } 36 37 public void setId(String id) { 38 this.id = id; 39 } 40 41 public String getRoleName() { 42 return roleName; 43 } 44 45 public void setRoleName(String roleName) { 46 this.roleName = roleName; 47 } 48 49 public List<Resource> getResources() { 50 return resources; 51 } 52 53 public void setResources(List<Resource> resources) { 54 this.resources = resources; 55 } 56 57 public List<User> getUsers() { 58 return users; 59 } 60 61 public void setUsers(List<User> users) { 62 this.users = users; 63 } 64 }
權限:
github
1 package com.cas.client1.entity; 2 3 import javax.persistence.Column; 4 import javax.persistence.Entity; 5 import javax.persistence.Id; 6 import javax.persistence.Table; 7 8 @Entity 9 @Table(name = "s_resource") 10 public class Resource { 11 @Id 12 @Column(name = "id") 13 private String id; 14 @Column(name = "res_name") 15 private String resName; 16 @Column(name = "res_code") 17 private String resCode; 18 @Column(name = "url") 19 private String url; 20 @Column(name = "priority") 21 private String priority; 22 23 public String getId() { 24 return id; 25 } 26 27 public void setId(String id) { 28 this.id = id; 29 } 30 31 public String getResName() { 32 return resName; 33 } 34 35 public void setResName(String resName) { 36 this.resName = resName; 37 } 38 39 public String getResCode() { 40 return resCode; 41 } 42 43 public void setResCode(String resCode) { 44 this.resCode = resCode; 45 } 46 47 public String getUrl() { 48 return url; 49 } 50 51 public void setUrl(String url) { 52 this.url = url; 53 } 54 55 public String getPriority() { 56 return priority; 57 } 58 59 public void setPriority(String priority) { 60 this.priority = priority; 61 } 62 }
創建幾個DAO
UserDao:
web
1 package com.cas.client1.dao; 2 3 import com.cas.client1.entity.User; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 7 import org.springframework.stereotype.Repository; 8 9 import java.util.List; 10 11 @Repository 12 public interface UserDao extends JpaRepository<User,String> { 13 @Override 14 List<User> findAll(); 15 16 List<User> findByUsername(String username); 17 18 /** 19 * 根據用戶名like查詢 20 * @param username 21 * @return 22 */ 23 List<User> getUserByUsernameContains(String username); 24 25 @Query("from User where id=:id") 26 User getUserById(@Param("id") String id); 27 28 }
ResourceDao:spring
1 package com.cas.client1.dao; 2 3 import com.cas.client1.entity.Resource; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.stereotype.Repository; 7 8 import java.util.List; 9 10 /** 11 * @author Administrator 12 */ 13 @Repository 14 public interface ResourceDao extends JpaRepository<Resource,String> { 15 16 @Query("from Resource order by priority") 17 List<Resource> getAllResource(); 18 }
Service
UserService:sql
1 package com.cas.client1.service; 2 3 import com.cas.client1.dao.UserDao; 4 import com.cas.client1.entity.User; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 8 import java.util.List; 9 10 @Service 11 public class UserService { 12 @Autowired 13 private UserDao userDao; 14 15 public User findByUsername(String username){ 16 List<User> list = userDao.findByUsername(username); 17 return list!=null&&list.size()>0?list.get(0):null; 18 } 19 }
ResourceService:
1 package com.cas.client1.service; 2 3 import com.cas.client1.dao.ResourceDao; 4 import com.cas.client1.entity.Resource; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 8 import java.util.List; 9 10 @Service 11 public class ResourceService { 12 @Autowired 13 private ResourceDao resourceDao; 14 15 public List<Resource> getAll(){ 16 return resourceDao.getAllResource(); 17 } 18 }
建立UserDetailsServiceImpl,實現UserDetailsService接口,這個類是用以提供給Spring security從數據庫加載用戶信息的
1 package com.cas.client1.security; 2 3 import com.cas.client1.entity.User; 4 import com.cas.client1.service.UserService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; 7 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; 8 import org.springframework.security.core.userdetails.UserDetails; 9 import org.springframework.security.core.userdetails.UserDetailsService; 10 import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 import org.springframework.stereotype.Component; 12 13 /** 14 * @author Administrator 15 */ 16 @SuppressWarnings("ALL") 17 @Component 18 public class UserDetailsServiceImpl implements UserDetailsService{ 19 @Autowired 20 private UserService userService; 21 @Override 22 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 23 User user = userService.findByUsername(username); 24 return user; 25 } 26 27 28 }
記得加@Component註解,以把實例交由Spring管理,或@Service,大家喜歡就好
建立SecurityMetaDataSource類
該類實現Spring security的FilterInvocationSecurityMetadataSource接口,做用是提供權限的元數據定義,並根據請求url匹配該url所須要的權限,獲取權限後交由AccessDecisionManager的實現者裁定可否訪問這個url,不能則會返回403的http錯誤碼
SecurityMetaDataSource:
1 package com.cas.client1.security; 2 3 import com.cas.client1.entity.Resource; 4 import com.cas.client1.service.ResourceService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.security.access.AccessDecisionManager; 7 import org.springframework.security.access.ConfigAttribute; 8 import org.springframework.security.access.SecurityConfig; 9 import org.springframework.security.access.intercept.AbstractSecurityInterceptor; 10 import org.springframework.security.web.FilterInvocation; 11 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 12 import org.springframework.security.web.util.matcher.AndRequestMatcher; 13 import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 14 import org.springframework.security.web.util.matcher.RequestMatcher; 15 import org.springframework.stereotype.Component; 16 17 import javax.annotation.PostConstruct; 18 import java.util.*; 19 20 @Component 21 public class SecurityMetaDataSource implements FilterInvocationSecurityMetadataSource { 22 23 @Autowired 24 private ResourceService resourceService; 25 26 private LinkedHashMap<String,Collection<ConfigAttribute>> metaData; 27 @PostConstruct 28 private void loadSecurityMetaData(){ 29 List<Resource> list = resourceService.getAll(); 30 metaData=new LinkedHashMap<>(); 31 for (Resource resource:list){ 32 List<ConfigAttribute> attributes=new ArrayList<>(); 33 attributes.add(new SecurityConfig(resource.getResCode())); 34 metaData.put(resource.getUrl(),attributes); 35 } 36 List<ConfigAttribute> base=new ArrayList<>(); 37 base.add(new SecurityConfig("AUTH_0")); 38 metaData.put("/**",base); 39 } 40 41 @Override 42 public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { 43 FilterInvocation invocation= (FilterInvocation) object; 44 if (metaData==null){ 45 return new ArrayList<>(0); 46 } 47 String requestUrl = invocation.getRequestUrl(); 48 System.out.println("請求Url:"+requestUrl); 49 Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iterator = metaData.entrySet().iterator(); 50 Collection<ConfigAttribute> rs=new ArrayList<>(); 51 while (iterator.hasNext()){ 52 Map.Entry<String, Collection<ConfigAttribute>> next = iterator.next(); 53 String url = next.getKey(); 54 Collection<ConfigAttribute> value = next.getValue(); 55 RequestMatcher requestMatcher=new AntPathRequestMatcher(url); 56 if (requestMatcher.matches(invocation.getRequest())){ 57 rs = value; 58 break; 59 } 60 } 61 System.out.println("攔截認證權限爲:"+rs); 62 return rs; 63 } 64 65 @Override 66 public Collection<ConfigAttribute> getAllConfigAttributes() { 67 System.out.println("invoke getAllConfigAttributes "); 68 //loadSecurityMetaData(); 69 //System.out.println("初始化元數據"); 70 Collection<Collection<ConfigAttribute>> values = metaData.values(); 71 Collection<ConfigAttribute> all=new ArrayList<>(); 72 for (Collection<ConfigAttribute> each:values){ 73 each.forEach(configAttribute -> { 74 all.add(configAttribute); 75 }); 76 } 77 return all; 78 } 79 80 @Override 81 public boolean supports(Class<?> clazz) { 82 return true; 83 } 84 }
同理:記得加上@Component註解
重頭戲來了!Spring security的配置
建立SpringSecurityConfig類
該類繼承於WebSecurityConfigurerAdapter,核心的配置類,在這裏定義Spring security的使用方式
SpringSecurityConfig
1 package com.cas.client1.security; 2 3 import com.cas.client1.config.CasProperties; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.security.access.AccessDecisionManager; 8 import org.springframework.security.access.AccessDecisionVoter; 9 import org.springframework.security.access.vote.AffirmativeBased; 10 import org.springframework.security.access.vote.RoleVoter; 11 import org.springframework.security.authentication.AuthenticationManager; 12 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 13 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 import org.springframework.security.config.annotation.web.builders.WebSecurity; 15 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 16 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 17 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 18 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; 19 20 import java.util.ArrayList; 21 import java.util.List; 22 23 /** 24 * Spring security配置 25 * @author youyp 26 * @date 2018-8-10 27 */ 28 @SuppressWarnings("ALL") 29 @Configuration 30 @EnableWebSecurity 31 public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { 32 @Autowired 33 private UserDetailsServiceImpl userDetailsService; 34 35 @Autowired 36 private SecurityMetaDataSource securityMetaDataSource; 37 38 @Override 39 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 40 super.configure(auth); 41 } 42 43 @Override 44 public void configure(WebSecurity web) throws Exception { 45 web.ignoring().antMatchers("/js/**","/css/**","/img/**","/*.ico","/login.html", 46 "/error","/login.do"); 47 } 48 49 @Override 50 protected void configure(HttpSecurity http) throws Exception { 51 System.out.println("配置Spring security"); 52 http.formLogin() 53 //指定登陸頁是」/login」 54 .loginPage("/login.html").permitAll() 55 .loginProcessingUrl("/login.do").permitAll() 56 .defaultSuccessUrl("/home",true) 57 .permitAll() 58 //登陸成功後可以使用loginSuccessHandler()存儲用戶信息,可選。 59 //.successHandler(loginSuccessHandler()).permitAll() 60 .and() 61 .logout().permitAll() 62 .invalidateHttpSession(true) 63 .and() 64 //登陸後記住用戶,下次自動登陸,數據庫中必須存在名爲persistent_logins的表 65 .rememberMe() 66 .tokenValiditySeconds(1209600) 67 .and() 68 .csrf().disable() 69 //其餘全部資源都須要認證,登錄後訪問 70 .authorizeRequests().anyRequest().fullyAuthenticated(); 71 72 http.addFilterBefore(filterSecurityInterceptor(),FilterSecurityInterceptor.class); 73 } 74 75 /** 76 * 注意:這裏不能加@Bean註解 77 * @return 78 * @throws Exception 79 */ 80 //@Bean 81 public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception { 82 FilterSecurityInterceptor filterSecurityInterceptor=new FilterSecurityInterceptor(); 83 filterSecurityInterceptor.setSecurityMetadataSource(securityMetaDataSource); 84 filterSecurityInterceptor.setAuthenticationManager(authenticationManager()); 85 filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased()); 86 return filterSecurityInterceptor; 87 } 88 89 90 /** 91 * 重寫AuthenticationManager獲取的方法而且定義爲Bean 92 * @return 93 * @throws Exception 94 */ 95 @Override 96 @Bean 97 public AuthenticationManager authenticationManagerBean() throws Exception { 98 return super.authenticationManagerBean(); 99 } 100 101 @Autowired 102 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 103 //指定密碼加密所使用的加密器爲passwordEncoder() 104 //須要將密碼加密後寫入數據庫 105 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 106 auth.eraseCredentials(false); 107 } 108 109 @Bean 110 public BCryptPasswordEncoder passwordEncoder() { 111 112 return new BCryptPasswordEncoder(4); 113 } 114 115 116 /** 117 * 定義決策管理器,這裏可直接使用內置的AffirmativeBased選舉器, 118 * 若是須要,可自定義,繼承AbstractAccessDecisionManager,實現decide方法便可 119 * @return 120 */ 121 @Bean 122 public AccessDecisionManager affirmativeBased(){ 123 List<AccessDecisionVoter<? extends Object>> voters=new ArrayList<>(); 124 voters.add(roleVoter()); 125 System.out.println("正在建立決策管理器"); 126 return new AffirmativeBased(voters); 127 } 128 129 /** 130 * 定義選舉器 131 * @return 132 */ 133 @Bean 134 public RoleVoter roleVoter(){ 135 //這裏使用角色選舉器 136 RoleVoter voter=new RoleVoter(); 137 System.out.println("正在建立選舉器"); 138 voter.setRolePrefix("AUTH_"); 139 System.out.println("已將角色選舉器的前綴修改成AUTH_"); 140 return voter; 141 } 142 143 }
說一個注意點:
FilterSecurityInterceptor這個過濾器最爲重要,它負責數據庫權限信息加載,權限鑑定等關鍵動做,這個過濾器位於SpringSecurityFilterChain,即Spring security的過濾器鏈中,若是將這個類在配置類中加了@Bean註解,那麼它將直接加入web容器的過濾器鏈中,這個鏈是首層過濾器鏈,
進入這個過濾器鏈以後纔會進入SpringSecurityFilterChain這個負責安全的鏈條,若是這個跑到外層去了,就會致使這個獨有的過濾器一直在生效,請求無限被攔截重定向,由於這個過濾器前面沒有別的過濾器阻止它生效,若是它位於SpringSecurityFilterChain中,在進入FilterSecurityInterceptor這個
過濾器以前會有不少的Spring security過濾器在生效,若是不知足前面的過濾器的條件,不會進入到這個過濾器。也就是說,要進入到這個過濾器,必需要從SpringSecurityFilterChain進入,從其餘地方進入都會致使請求被無限重定向
另外
FilterSecurityInterceptor這個類繼承於AbstractSecurityInterceptor並實現Filter接口,由此咱們能夠重寫該類,自定義咱們的特殊業務,可是,我的以爲FilterSecurityInterceptor這個實現類已經很完整地實現了這個過濾器應作的工做,沒有必要重寫
相似的,還有AccessDecisionManager這個「決策者」,Spring security爲這個功能提供了幾個默認的實現者,如AffirmativeBased這個類,是一個基於投票的決策器,投票器(Voter)要求實現AccessDecisionVoter接口,Spring security已爲咱們提供了幾個頗有用的投票器如RoleVoter,WebExpressionVoter
這些咱們都沒有必要去自定義,並且自定義出來的也沒有默認實現拓展性和穩定性更好
再定義一個登錄的Controller
LoginController
1 package com.cas.client2.casclient2.controller; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.security.authentication.AuthenticationManager; 5 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 import org.springframework.security.cas.authentication.CasAuthenticationToken; 7 import org.springframework.security.cas.web.CasAuthenticationFilter; 8 import org.springframework.security.core.Authentication; 9 import org.springframework.security.core.context.SecurityContextHolder; 10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 13 import javax.servlet.http.HttpSession; 14 15 @SuppressWarnings("ALL") 16 @Controller 17 public class LoginController { 18 @Autowired 19 private AuthenticationManager authenticationManager; 20 21 /** 22 * 自定義登陸地址 23 * @param username 24 * @param password 25 * @param session 26 * @return 27 */ 28 @RequestMapping("login.do") 29 public String login(String username,String passwod, HttpSession session){ 30 try { 31 System.out.println("進入登陸請求.........."); 32 UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(username,passwod); 33 34 Authentication authentication=authenticationManager.authenticate(token); 35 SecurityContextHolder.getContext().setAuthentication(authentication); 36 session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); 37 System.out.println("登陸成功"); 38 return "redirect:home.html"; 39 }catch (Exception e){ 40 e.printStackTrace(); 41 return "login.html"; 42 } 43 44 } 45 }
建立幾個頁面:在resources下建立文件夾html,用於存放html靜態文件,
home.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>HOME</title> 6 </head> 7 <body> 8 <h1>welcome to Home</h1> 9 <button onclick="javascript:location.href='/logout'">退出</button> 10 </body> 11 </html>
login.html
1 <!DOCTYPE html> 2 <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" 3 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>登陸</title> 7 </head> 8 9 <body> 10 <span style="color: red" id="msg"></span> 11 <form action="/login.do" method="post"> 12 <div><label> User Name : <input type="text" name="username"/> </label></div> 13 <div><label> Password: <input type="password" name="password"/> </label></div> 14 <div><input type="submit" value="Sign In"/></div> 15 <input type="checkbox" name="remember-me" value="true" th:checked="checked"/><p>Remember me</p> 16 </form> 17 18 </body> 19 <script type="text/javascript"> 20 var url=location.href 21 var param=url.split("?")[1]; 22 console.log(param); 23 if (param){ 24 var p=param.split("&"); 25 var msg=p[0].split("=")[1]; 26 document.getElementById("msg").innerHTML=msg; 27 } 28 </script> 29 </html>
admin.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>admin</title> </head> <body> 你好,歡迎登錄,這是管理員界面,擁有/admin.html的訪問權限才能訪問 </body> </html>
再定義幾個錯誤頁面
在html文件夾下建立一個error文件夾,在error文件夾中建立403.html,404.html,500.html;在程序遇到這些錯誤碼時,會自動跳轉到對應的頁面
先啓動一下項目,讓spring-data-jpa反向生成一下表結構
再往數據庫插入幾條數據:
用戶表的密碼須要放密文,咱們把咱們的明文密碼使用咱們的密碼encoder轉一下:BCryptPasswordEncoder.encode("123");獲得密文後存到數據庫的password字段中
用戶表:
資源表:即權限信息表
角色表:
角色權限中間表:
咱們先不給用戶配置角色,如今是空角色
啓動Spring boot啓動類,訪問localhost:8083,檢測到沒登陸會自動跳到登陸頁面,登陸後自動跳轉到home.html
訪問admin.html,返回403頁面,當前用戶無權限訪問
再將剛剛的角色分配給用戶,再次訪問
此時即可訪問,大功告成!
報了個警告,說咱們沒有配置ssl,也就是須要配置https,不過能夠不用配置,
咱們能夠配置使用http:
設置cas server使用http非安全協議
主要有如下步驟:
1.WEB-INF/deployerConfigContext.xml中在< bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" />增長參數 p:requireSecure="false" ,是否須要安全驗證,即 HTTPS,false 爲不採用 以下:< bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" p:requireSecure= "false" />
1. WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml中將p:cookieSecure="true"修改成 p:cookieSecure="false"
2. WEB-INF/spring-configuration/warnCookieGenerator.xml中將p:cookieSecure="true"改成p:cookieSecure="false"
3. 在tomcat的server.xml中關閉8443端口,以下圖
1 <!-- security 對CAS支持 --> 2 <dependency> 3 <groupId>org.springframework.security</groupId> 4 <artifactId>spring-security-cas</artifactId> 5 </dependency>
修改一下咱們的UserDetailsServiceImpl類,讓它實現AuthenticationUserDetailsService<CasAssertionAuthenticationToken>接口
UserDetailsServiceImpl:
1 package com.cas.client1.security; 2 3 import com.cas.client1.entity.User; 4 import com.cas.client1.service.UserService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken; 7 import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; 8 import org.springframework.security.core.userdetails.UserDetails; 9 import org.springframework.security.core.userdetails.UserDetailsService; 10 import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 import org.springframework.stereotype.Component; 12 13 /** 14 * @author Administrator 15 */ 16 @SuppressWarnings("ALL") 17 @Component 18 public class UserDetailsServiceImpl implements UserDetailsService, 19 AuthenticationUserDetailsService<CasAssertionAuthenticationToken> { 20 @Autowired 21 private UserService userService; 22 @Override 23 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 24 User user = userService.findByUsername(username); 25 return user; 26 } 27 28 /** 29 * 實現AuthenticationUserDetailsService的方法, 30 * 用於獲取cas server返回的用戶信息,再根據用戶關鍵信息加載出用戶在當前系統的權限 31 * @param token 32 * @return 33 * @throws UsernameNotFoundException 34 */ 35 @Override 36 public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException { 37 String name = token.getName(); 38 System.out.println("得到的用戶名:"+name); 39 User user = userService.findByUsername(name); 40 if (user==null){ 41 throw new UsernameNotFoundException(name+"不存在"); 42 } 43 return user; 44 } 45 }
在application.properties文件中加上如下內容:
1 # cas服務器地址 2 cas.server.host.url=http://localhost:8081/cas 3 # cas服務器登陸地址 4 cas.server.host.login_url=${cas.server.host.url}/login 5 # cas服務器登出地址 6 cas.server.host.logout_url=${cas.server.host.url}/logout?service=${app.server.host.url} 7 # 應用訪問地址 8 app.server.host.url=http://localhost:8083 9 # 應用登陸地址 10 app.login.url=/login.do 11 # 應用登出地址 12 app.logout.url=/logout
新增一個配置實體類
CasProperties
package com.cas.client1.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class CasProperties { @Value("${cas.server.host.url}") private String casServerUrl; @Value("${cas.server.host.login_url}") private String casServerLoginUrl; @Value("${cas.server.host.logout_url}") private String casServerLogoutUrl; @Value("${app.server.host.url}") private String appServerUrl; @Value("${app.login.url}") private String appLoginUrl; @Value("${app.logout.url}") private String appLogoutUrl; /**get set方法略 */ }
再修改一下咱們的Spring security配置類
1 package com.cas.client1.security; 2 3 import com.cas.client1.config.CasProperties; 4 import org.jasig.cas.client.session.SingleSignOutFilter; 5 import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.context.annotation.Bean; 8 import org.springframework.context.annotation.Configuration; 9 import org.springframework.http.HttpMethod; 10 import org.springframework.security.access.AccessDecisionManager; 11 import org.springframework.security.access.AccessDecisionVoter; 12 import org.springframework.security.access.vote.AffirmativeBased; 13 import org.springframework.security.access.vote.RoleVoter; 14 import org.springframework.security.authentication.AuthenticationManager; 15 import org.springframework.security.cas.ServiceProperties; 16 import org.springframework.security.cas.authentication.CasAuthenticationProvider; 17 import org.springframework.security.cas.web.CasAuthenticationEntryPoint; 18 import org.springframework.security.cas.web.CasAuthenticationFilter; 19 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 20 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 21 import org.springframework.security.config.annotation.web.builders.WebSecurity; 22 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 23 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 24 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 25 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; 26 import org.springframework.security.web.authentication.logout.LogoutFilter; 27 import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Spring security配置 34 * @author youyp 35 * @date 2018-8-10 36 */ 37 @SuppressWarnings("ALL") 38 @Configuration 39 @EnableWebSecurity 40 public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { 41 @Autowired 42 private CasProperties casProperties; 43 44 @Autowired 45 private UserDetailsServiceImpl userDetailsService; 46 47 @Autowired 48 private SecurityMetaDataSource securityMetaDataSource; 49 50 @Override 51 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 52 super.configure(auth); 53 auth.authenticationProvider(casAuthenticationProvider()); 54 } 55 56 @Override 57 public void configure(WebSecurity web) throws Exception { 58 web.ignoring().antMatchers("/js/**","/css/**","/img/**","/*.ico","/login.html", 59 "/error","/login.do"); 60 //web.ignoring().antMatchers("/js/**","/css/**","/img/**","/*.ico",,"/home"); 61 //web.ignoring().antMatchers("/**"); 62 // super.configure(web); 63 64 } 65 66 @Override 67 protected void configure(HttpSecurity http) throws Exception { 68 System.out.println("配置Spring security"); 69 http.formLogin() 70 //指定登陸頁是」/login」 71 //.loginPage("/login.html").permitAll() 72 //.loginProcessingUrl("/login.do").permitAll() 73 //.defaultSuccessUrl("/home",true) 74 //.permitAll() 75 //登陸成功後可以使用loginSuccessHandler()存儲用戶信息,可選。 76 //.successHandler(loginSuccessHandler()).permitAll() 77 .and() 78 .logout().permitAll() 79 //退出登陸後的默認網址是」/home」 80 //.logoutSuccessUrl("/home.html") 81 //.permitAll() 82 .invalidateHttpSession(true) 83 .and() 84 //登陸後記住用戶,下次自動登陸,數據庫中必須存在名爲persistent_logins的表 85 .rememberMe() 86 .tokenValiditySeconds(1209600) 87 .and() 88 .csrf().disable() 89 //其餘全部資源都須要認證,登錄後訪問 90 .authorizeRequests().anyRequest().fullyAuthenticated(); 91 http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint()) 92 .and() 93 .addFilterAt(casAuthenticationFilter(),CasAuthenticationFilter.class) 94 .addFilterBefore(casLogoutFilter(),LogoutFilter.class) 95 .addFilterBefore(singleSignOutFilter(),CasAuthenticationFilter.class); 96 /** 97 * FilterSecurityInterceptor自己屬於過濾器,不能在外面定義爲@Bean, 98 * 若是定義在外面,則這個過濾器會被獨立加載到webContext中,致使請求會一直被這個過濾器攔截 99 * 加入到Springsecurity的過濾器鏈中,纔會使它完整的生效 100 */ 101 http.addFilterBefore(filterSecurityInterceptor(),FilterSecurityInterceptor.class); 102 } 103 104 /** 105 * 注意:這裏不能加@Bean註解 106 * @return 107 * @throws Exception 108 */ 109 // @Bean 110 public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception { 111 FilterSecurityInterceptor filterSecurityInterceptor=new FilterSecurityInterceptor(); 112 filterSecurityInterceptor.setSecurityMetadataSource(securityMetaDataSource); 113 filterSecurityInterceptor.setAuthenticationManager(authenticationManager()); 114 filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased()); 115 return filterSecurityInterceptor; 116 } 117 118 /** 119 * 認證入口 120 * <p> 121 * <b>Note:</b>瀏覽器訪問不可直接填客戶端的login請求,若如此則會返回Error頁面,沒法被此入口攔截 122 * </p> 123 * @return 124 */ 125 @Bean 126 public CasAuthenticationEntryPoint casAuthenticationEntryPoint(){ 127 CasAuthenticationEntryPoint casAuthenticationEntryPoint=new CasAuthenticationEntryPoint(); 128 casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl()); 129 casAuthenticationEntryPoint.setServiceProperties(serviceProperties()); 130 return casAuthenticationEntryPoint; 131 } 132 133 @Bean 134 public ServiceProperties serviceProperties() { 135 ServiceProperties serviceProperties=new ServiceProperties(); 136 serviceProperties.setService(casProperties.getAppServerUrl()+casProperties.getAppLoginUrl()); 137 serviceProperties.setAuthenticateAllArtifacts(true); 138 return serviceProperties; 139 } 140 141 // @Bean 142 public CasAuthenticationFilter casAuthenticationFilter() throws Exception { 143 CasAuthenticationFilter casAuthenticationFilter=new CasAuthenticationFilter(); 144 casAuthenticationFilter.setAuthenticationManager(authenticationManager()); 145 casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl()); 146 // casAuthenticationFilter.setAuthenticationSuccessHandler( 147 // new SimpleUrlAuthenticationSuccessHandler("/home.html")); 148 return casAuthenticationFilter; 149 } 150 151 @Bean 152 public CasAuthenticationProvider casAuthenticationProvider(){ 153 CasAuthenticationProvider casAuthenticationProvider=new CasAuthenticationProvider(); 154 casAuthenticationProvider.setAuthenticationUserDetailsService(userDetailsService); 155 156 casAuthenticationProvider.setServiceProperties(serviceProperties()); 157 casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator()); 158 casAuthenticationProvider.setKey("casAuthenticationProviderKey"); 159 return casAuthenticationProvider; 160 } 161 162 @Bean 163 public Cas20ServiceTicketValidator cas20ServiceTicketValidator() { 164 return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl()); 165 } 166 167 // @Bean 168 public SingleSignOutFilter singleSignOutFilter(){ 169 SingleSignOutFilter singleSignOutFilter=new SingleSignOutFilter(); 170 singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl()); 171 singleSignOutFilter.setIgnoreInitConfiguration(true); 172 return singleSignOutFilter; 173 } 174 175 // @Bean 176 public LogoutFilter casLogoutFilter(){ 177 LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler()); 178 logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl()); 179 return logoutFilter; 180 } 181 182 /** 183 * 重寫AuthenticationManager獲取的方法而且定義爲Bean 184 * @return 185 * @throws Exception 186 */ 187 @Override 188 @Bean 189 public AuthenticationManager authenticationManagerBean() throws Exception { 190 return super.authenticationManagerBean(); 191 } 192 193 @Autowired 194 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 195 //指定密碼加密所使用的加密器爲passwordEncoder() 196 //須要將密碼加密後寫入數據庫 197 //auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 198 //auth.eraseCredentials(false); 199 } 200 201 @Bean 202 public BCryptPasswordEncoder passwordEncoder() { 203 204 return new BCryptPasswordEncoder(4); 205 } 206 207 208 /** 209 * 定義決策管理器,這裏可直接使用內置的AffirmativeBased選舉器, 210 * 若是須要,可自定義,繼承AbstractAccessDecisionManager,實現decide方法便可 211 * @return 212 */ 213 @Bean 214 public AccessDecisionManager affirmativeBased(){ 215 List<AccessDecisionVoter<? extends Object>> voters=new ArrayList<>(); 216 voters.add(roleVoter()); 217 System.out.println("正在建立決策管理器"); 218 return new AffirmativeBased(voters); 219 } 220 221 /** 222 * 定義選舉器 223 * @return 224 */ 225 @Bean 226 public RoleVoter roleVoter(){ 227 //這裏使用角色選舉器 228 RoleVoter voter=new RoleVoter(); 229 System.out.println("正在建立選舉器"); 230 voter.setRolePrefix("AUTH_"); 231 System.out.println("已將角色選舉器的前綴修改成AUTH_"); 232 return voter; 233 } 234 235 236 @Bean 237 public LoginSuccessHandler loginSuccessHandler() { 238 return new LoginSuccessHandler(); 239 } 240 241 242 }
這裏咱們新增了幾個filter,請注意,這幾個filter定義時都不能配置@Bean註解,緣由以上相同,這幾個filter都要加入到springSecurity的FilterChain中,而不是直接加入到web容器的FilterChain中
再修改一下LoginController
1 package com.cas.client1.controller; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.security.authentication.AuthenticationManager; 5 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 import org.springframework.security.cas.web.CasAuthenticationFilter; 7 import org.springframework.security.core.Authentication; 8 import org.springframework.security.core.context.SecurityContextHolder; 9 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 13 import javax.servlet.http.HttpSession; 14 15 @SuppressWarnings("Duplicates") 16 @Controller 17 public class LoginController { 18 @Autowired 19 private AuthenticationManager authenticationManager; 20 21 /** 22 * 自定義登陸地址 23 * @param username 24 * @param password 25 * @param session 26 * @return 27 */ 28 @RequestMapping("login.do") 29 public String login(String ticket, HttpSession session){ 30 try { 31 System.out.println("進入登陸請求.........."); 32 //cas單點登陸的用戶名就是:_cas_stateful_ ,用戶憑證是server傳回來的ticket 33 String username = CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER; 34 UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(username,ticket); 35 Authentication authentication=authenticationManager.authenticate(token); 36 SecurityContextHolder.getContext().setAuthentication(authentication); 37 session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); 38 System.out.println("登陸成功"); 39 return "redirect:home.html"; 40 }catch (Exception e){ 41 e.printStackTrace(); 42 return "login.html"; 43 } 44 45 } 46 }
這時,以前負責登陸的loginController再也不是驗證用戶名和密碼正不正確了,由於用戶名密碼的驗證已經交給cas server了,LoginController的工做就是接收cas server重定向時傳回來的ticket,驗證ticket的有效性,若是沒有異常,則會進入到UserDetailsServiceImpl中的loadUserDetails方法,並根據用戶名加載用戶權限等信息,而後咱們再將用戶信息存入Session,完成本地登陸,本地登陸以後,用戶每次請求時,就不須要再次驗證ticket了,而是驗證Session
到這裏,cas client已經配置完成,爲了看清楚流程,咱們以debug模式啓動一下項目,在loginController的login方法開頭打一個斷點,打開瀏覽器調試模式(F12),切換到network看請求,在瀏覽器中輸入:localhost:8083,瀏覽器會自動重定向到cas server 的登陸頁面,以下圖:
咱們輸入一個數據庫中有的用戶名,再在密碼欄中輸入一次用戶名,由於這裏的cas server驗證方式還沒改,只要求用戶名和密碼相同就可經過驗證,後面我會研究一下怎麼修改cas server 的驗證方式爲數據庫驗證
如輸入:用戶名:user 密碼:user
點擊登陸,驗證成功後,咱們看F12 network請求,發現瀏覽器發送了兩個請求,一個是8081的,也就是cas server的,另一個是8083的,也就是咱們的client端的,如圖:
另外一個
由於咱們在後臺開了debug模式,打了斷點,因此後面這個請求一直在pending狀態,咱們先看第一個請求的詳細狀況:
很明顯的,這個請求發送了咱們的用戶名和密碼,由此可知,這個請求的做用就是負責在cas server後臺驗證用戶名的密碼,驗證成功後,會自動重定向到第二個請求
咱們再來看第二個請求:
這個請求就是咱們cas client所配置的登陸地址,此時這個請求後面自動帶上了一個名爲ticket的參數,參數值是一串自動生成的隨機字符串,由cas server生成的
咱們再回到後臺,沒什麼錯誤的話,咱們能夠看到LoginController接收到了這個參數,咱們先在UserDetailsServiceImpl類的loadUserDetails方法的開頭打一個斷點,按F8讓調試器跑走,此時,咱們就能夠看到調試器跳到了咱們剛剛打的UserDetasServiceImpl的斷點中,再看看參數
能夠看出,咱們接收到了cas server認證完ticket後傳回來的用戶名,咱們根據用戶名加載對應的權限,返回便可,此時咱們再次按F8跳走
再回到界面,發現咱們已經能夠訪問頁面了:
下一步,就是驗證多個應用之間是否能只登錄一次就不用再登錄了;
咱們將當前項目拷貝一份,更名稱爲cas-client2(maven的groupId和artifactId),再修改一下端口爲8082,,記得對應的cas配置也要改:
啓動項目
先訪問localhost:8082
發現它自動跳轉到了8081的cas server
再打開另一個瀏覽器標籤,訪問localhost:8083
發現它也自動跳到了cas的登陸頁面,咱們先在這裏輸入帳號密碼登陸:
登陸成功後,咱們再切換回剛剛沒登陸的8082的網頁標籤,刷新一下,
ok,8082也不用登錄了,大功告成!
源碼地址:
https://github.com/yupingyou/casclient.git
另:Spring security本來默認有個/login和/logout的handler,(之前不是這個地址,不知道從哪一個版本開始改了,之前好像是_spring_security_check,大概是這個,記不太清,我用了4之後就發現地址變了),可是我發現我訪問/login的時候出現404,但/logout能夠訪問,沒發現什麼緣由,後來我就自定義一個登錄了,也就是我配置的/login.do,代替了默認的/login第一次動手寫這麼長的博客...............累