Spring Security
準備工作
使用Maven搭建SpringMVC項目
添加Spring Security支持
添加相關Jar
<spring.security.version>4.2.2.RELEASE</spring.security.version> ..... <!-- spring security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency>
配置web.xml
添加filter DelegatingFilterProxy到web.xml
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
自定義WebSecurityConfig
@Configuration @EnableWebSecurity //添加annotation 支持,包括(prePostEnabled,securedEnabled...) @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() //所有用戶可以訪問"/resources"目錄下的資源以及訪問"/home"和favicon.ico .antMatchers("/resources/**", "/home","/**/favicon.ico").permitAll() //以"/admin"開始的URL,並需擁有 "ROLE_ADMIN" 角色權限,這裏用hasRole不需要寫"ROLE_"前綴,會自動加上 .antMatchers("/admin/**").hasRole("ADMIN") //以"/admin"開始的URL,並需擁有 "ROLE_ADMIN" 角色權限和 "ROLE_DBA" 角色,這裏不需要寫"ROLE_"前綴; .antMatchers("/dba/**").access("hasRole('ADMIN') and hasRole('DBA')") //前面沒有匹配上的請求,全部需要認證; .anyRequest().authenticated() .and() //指定登錄界面,並且設置爲所有人都能訪問; .formLogin().loginPage("/login").permitAll() //如果登錄失敗會跳轉到"/hello" .successForwardUrl("/hello") //如果登錄失敗會跳轉到"/logout" //.failureForwardUrl("/logout") .and() .logout() .logoutUrl("/admin/logout") //指定登出的地址,默認是"/logout" .logoutSuccessUrl("/admin/index") //登出後的跳轉地址login?logout //.logoutSuccessHandler(logoutSuccessHandler) //自定義LogoutSuccessHandler,在登出成功後調用,如果被定義則logoutSuccessUrl()就會被忽略 .invalidateHttpSession(true) //定義登出時是否invalidate HttpSession,默認爲true //.addLogoutHandler(logoutHandler) //添加自定義的LogoutHandler,默認會添加SecurityContextLogoutHandler .deleteCookies("usernameCookie","urlCookie") //在登出同時清除cookies .and().anonymous() // .and() // .exceptionHandling() // .authenticationEntryPoint(new BasicAuthenticationEntryPoint()) // .accessDeniedHandler(accessDeniedHandler()); //測試發現accessDeniedHandler沒生效,只會報Exception,沒有跳轉到實際page ; } @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) //指定密碼加密形式,如數據庫中用戶密碼爲md5加密,這裏就用Md5PasswordEncoder .passwordEncoder(passwordEncoder()); } private Md5PasswordEncoder passwordEncoder() { return new Md5PasswordEncoder(); } private AccessDeniedHandler accessDeniedHandler(){ AccessDeniedHandlerImpl handler = new AccessDeniedHandlerImpl(); handler.setErrorPage("/login"); return handler; } }
創建User
創建軍User 類實現自 org.springframework.security.core.userdetails.UserDetails 接口,包含一組權限的集合 authorities。
它和我們的領域類可能有部分屬性重疊,可以把常用的部分字段存在裏面。方便登錄後調用。
public class User implements UserDetails { private String username; private String password; private List<Authority> authorities; //....省略了其他需要用到字段,如做賬號過期判斷用到的一些字段,以及一些登錄後常用的字段 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { //...省略了判斷邏輯 return true; } @Override public boolean isAccountNonLocked() { //...省略了判斷邏輯 return true; } @Override public boolean isCredentialsNonExpired() { //...省略了判斷邏輯 return true; } @Override public boolean isEnabled() { return true; } }
創建Authority
創建軍Authority 類實現自 org.springframework.security.core.GrantedAuthority 接口,定義權限。
getAuthority 方法只返回一個表示權限名稱的字符串
public class Authority implements GrantedAuthority { private static final long serialVersionUID = 1L; private String authority; public Authority() { } public Authority(String authority) { this.setAuthority(authority); } @Override public String getAuthority() { return this.authority; } public void setAuthority(String authority) { this.authority = authority; } }
創建UserDetailsServiceImpl
創建軍UserDetailsServiceImpl 類實現自 org.springframework.security.core.userdetails.UserDetailsService 接口。
loadUserByUsername(String username) 方法根據用戶名查詢符合條件的用戶,若沒有找到符合條件的用戶,必須拋出 UsernameNotFoundException 異常,而不能返回空。
這裏可以從數據庫查詢用戶,這裏只爲實驗寫死了數據。
@Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if(username.startsWith("a")){ User user = new User(username,"e10adc3949ba59abbe56e057f20f883e",Arrays.asList(new Authority("ROLE_ADMIN"))); return user; } if(username.startsWith("u")){ User user = new User(username,"e10adc3949ba59abbe56e057f20f883e",Arrays.asList(new Authority("ROLE_USER"))); return user; } if(username.startsWith("c")){ User user = new User(username,"e10adc3949ba59abbe56e057f20f883e",Arrays.asList(new Authority("ROLE_ANONYMOUS"))); return user; } if(username.startsWith("d")){ User user = new User(username,"e10adc3949ba59abbe56e057f20f883e", Arrays.asList( new Authority("ROLE_ANONYMOUS"), new Authority("ROLE_ADMIN"), new Authority("ROLE_USER"), new Authority("ROLE_DBA"))); return user; } throw new UsernameNotFoundException("用戶不存在!"); } }
前端代碼
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ; %> <!DOCTYPE html> <html lang="zh-cn"> <head> <title>Spring Security Example </title> </head> <body> <c:if test="${param.login ==''}"> <div>Invalid username and password.</div> </c:if> <script> console.log("${param.logout ==''}"); </script> <c:if test="${param.logout ==''}"> <div>You have been logged out.</div> </c:if> <form action="<%=basePath%>/login" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ; %> <!DOCTYPE html> <html lang="zh-cn"> <head> <title>Spring Security Example</title> </head> <body> <h1>Welcome!</h1> <p>Click <a href="<%=basePath%>/hello">here</a> to see a greeting.</p> </body> </html>
hello.jsp
該頁面包含一些security tgalib的使用,看代碼:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ; %> <!DOCTYPE html> <html lang="zh-cn"> <head> <title>Hello World!</title> </head> <body> <h1>Hello <sec:authentication property="principal.username" />!</h1> <form action="<%=basePath%>/admin/logout" method="post"> <input type="submit" value="Sign Out"/> </form> <sec:authorize access="hasRole('ADMIN')"> <span>the user has role 'ADMIN'</span><br> </sec:authorize> <sec:authorize access="hasRole('DBA')"> <span>the user has role 'DBA'</span><br> </sec:authorize> <sec:authorize access="hasRole('USER')"> <span>the user has role 'USER'</span><br> </sec:authorize> <sec:authorize url="/admin/hello"> This content will only be visible to users who are authorized to send requests to the "/admin" URL.<br> </sec:authorize> </body> </html>
Controller
在WebSecurityConfig中配置了@EnableGlobalMethodSecurity(prePostEnabled = true)
在Controller中可以直接使有註解的方式配置權限,和WebSecurityConfig中配置的權限是可以共存的。
@PreAuthorize("hasAuthority('ROLE_ADMIN')") @RequestMapping(value = "/home") public String home(Model model,HttpServletRequest request){ Authentication auth =(Authentication)request.getUserPrincipal(); UserDetails userDetails = (UserDetails)auth.getPrincipal(); auth.isAuthenticated(); return "home"; } @PreAuthorize("hasAnyRole('ADMIN','USER')") @RequestMapping(value = "/hello") public String hello(Model model,HttpServletRequest request){ return "hello"; }
可以通過HttpServletRequest取得當前用戶狀態和前面存在userDetail中的信息。
@PreAuthorize(“hasAnyRole('ADMIN','USER')”)配置了hello方法必須至少有“ADMIN”或“USER”中的一個角色,這裏不需要有ROLE_前綴,會自動加上。
@PreAuthorize(“hasRole('ADMIN')”) 配置必須有“ADMIN”角色權限
@PreAuthorize(“hasAuthority('ROLE_ADMIN')”) 配置必須有“ADMIN”角色權限,這裏用的hasAuthority,所以需要帶上ROLE_前綴
@PreAuthorize 可以放在方法名上,也可以加在Controller名上。