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名上。