Spring Security 快速入门

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


权限表达式


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章