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


權限表達式


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