1. 程式人生 > >Spring security 自定義登入與許可權控制

Spring security 自定義登入與許可權控制

一、先說必要的配置檔案:

1、web.xml檔案新增上

<!-- Spring Security 許可權框架 -->
<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>

2、application-context.xml配置檔案

在application-context配置檔案中加入spring security配置檔案的引用:

<?xml version="1.0" encoding="UTF-8"?>
<beans ......>   <bean>
<!-- Connection Info --> ......此處省略
</bean>
   <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
     ......<!-- if you want to use the Spring Velocity macros, set this property to true -->
<property name="exposeSpringMacroHelpers" value="true"/>
     <property 
name="contentType" value="text/html;charset=UTF-8"></property> <property name="toolboxConfigLocation"> <value>/WEB-INF/toolbox.xml</value> </property> </bean>
<import resource="/securityConfig.xml"/></beans>

3、securityConfig.xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
             http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://www.springframework.org/schema/security 
         http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<!--use-expressions="true" 的意思是開啟表示式 , access-denied-page的意思是,當驗證許可權失敗後會跳轉到的頁面 -->
<security:http auto-config="true" use-expressions="true" access-denied-page="/denied">      <!-- 對所有的資源,進行許可權的設定訪問-->
      <!-- 所有人都可以訪問資原始檔 -->
<security:intercept-url pattern="/login" access="permitAll" />
      <security:intercept-url pattern="/logout" access="permitAll" />
      <security:intercept-url pattern="/slr/prd/**" access="hasAnyRole('SHOW_PRD','ADD_PRD','MOD_PRD','DEL_PRD')" />
      <security:intercept-url pattern="/slr/order/**" access="hasAnyRole('SHOW_ORD','CONFIRM_ORD','CANCEL_ORD')" />
      <security:intercept-url pattern="/slr/mgm/**" access="hasAnyRole('SHOW_ACCT','ADD_ACCT','MOD_ACCT','DEL_ACCT')" />
      <!-- 配置登入頁面為login ,登入成功預設跳轉到welcome -->
      <security:form-login login-page="/login" default-target-url="/welcome"/>
      <security:session-management session-authentication-strategy-ref="concurrentSessionControlStrategy" />
   </security:http>
   <!--&lt;!&ndash; 阻止多端登入 &ndash;&gt;-->
   <bean id="concurrentSessionControlStrategy" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
       <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
       <property name="maximumSessions" value="1"></property>
   </bean>
   <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
   <!-- 配置一個認證管理器 -->
   <security:authentication-manager alias="authenticationManager">
<!-- 使用自定義的UserDetailService -->
<security:authentication-provider user-service-ref="userDetailsService">
      <security:password-encoder ref="passwordEncoder"/>
      </security:authentication-provider>
   </security:authentication-manager>
   <!--&lt;!&ndash; 對密碼進行MD5編碼 &ndash;&gt;-->
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
<!--  通過 userDetailsService,Spring會自動的定義使用者的訪問級別.   -->
   <bean id="userDetailsService" class="com.security.UserDetailsServiceImpl" />
</beans>


4、toolbox.xml檔案:

<toolbox>
   <data type="string">
      <key>version</key>
      <value>1.3</value>
   </data>
   ......
   <tool>
      <key>sec</key>
      <scope>request</scope>
      <class>com.security.SecurityVelocity</class>
   </tool>
</toolbox>

這樣在vm介面中就可以直接使用啦~

#if($sec.hasAnyRole("SHOW_PRD","ADD_PRD","MOD_PRD","DEL_PRD"))
          <h2>商品管理</h2>
          <ul>
              <li><a href="/slr/prd/list">商品管理</a></li>
          </ul>
#end

二、配置檔案中的hasAnyRole()方法和UserDetailService的實現

1.SecurityVelocity.java:

public class SecurityVelocity {
   
   public boolean hasAnyRole(String ...roles) {
      return isRole(roles);
}
   
    /***
     * 前端傳入陣列引數
* @param viewRole 可變陣列 1個或者多個
* @return 是否有許可權
*/
private boolean isRole(String ...roles){
       /** 前端陣列為空 */
if(null == roles || roles.length <= 0){
          return false;
}
       /** 獲取當前使用者登入物件 */
UserDetails userDetails = null;
       try {
          userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
         return false;
}
       if(null == userDetails){
          return false;
}
       /** 獲取當前使用者登入物件所有許可權 */
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
      if(null == authorities){
         return false;
}
      boolean flag = false;
Iterator<? extends GrantedAuthority> iter = authorities.iterator();
      while (iter.hasNext()) {
         GrantedAuthority grantedAuthority = iter.next();
/**遍歷前端列表的所有角色*/
for (String vr : roles) {
            if(vr.equals(grantedAuthority.getAuthority())){
               flag = true;
               break;
}
         }
         if (flag) break; // 已經匹配上角色了則不再需要匹配其它角色。
}
      return flag;
}
}

2.UserDetailServiceImpl.java

public class UserDetailsServiceImpl implements UserDetailsService {@Autowired
private UserAuthService userAuthService;
@Autowired
private UserService userService;
   public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
      
      User securityUser = null;
      UserVo mngVo = userService.getSellerByMobile(userName);
//許可權設定Collection<GrantedAuthority> grantedAuthorities = this.getGrantedAuthorities(mngVo);
        boolean enables = true;  
        boolean accountNonExpired = true;  
        boolean credentialsNonExpired = true;  
        boolean accountNonLocked = true; 
securityUser = new User(mngVo.getId().toString(), mngVo.getPassword(),
enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);  
        return securityUser;
}
   
   
   /**
     * 根據使用者獲取該使用者擁有的許可權
* @param user 當前使用者
* @return 使用者角色集合
*/
private Set<GrantedAuthority> getGrantedAuthorities(SellerVo user) {
        Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
        if(null != user){
            List<AuthorityVo> auths = userAuthService.getUserRoleListByUserId(user.getId());
             if(null != auths && auths.size() > 0 ) {
                 for(AuthorityVo auth : auths) {
                     grantedAuthorities.add(new SimpleGrantedAuthority(auth.getCode()+""));
}  
             }
        }
        return grantedAuthorities;  
}
   

}

這個類主要是在登入時呼叫來設定使用者許可權資訊的。

三:自己登入邏輯與springsecurity結合

public class LoginController extends BaseController {    ......
// show login page
@RequestMapping("login")
    public String index(Model model, HttpServletRequest request, HttpServletResponse response) {        
       // 判斷登入狀態是否還有效,有效的話自動登入
final UserVo  Vo = getLoginUser(request);
       if (userVo != null && userVo.getId() != null) {
  //登入資訊有效需要將許可權資訊放入SecurityContextHolder的上下文資訊中心,否則可能會造成迴圈重定向,伺服器報錯。
  Authentication authentication = (Authentication) request.getAttribute(CommonConstants.USER_AUTH);
SecurityContextHolder.getContext().setAuthentication(authentication);
// redirect to homepage
return "redirect:/";
}
//返回登入頁        return LOGIN;
}

    // do login action
@RequestMapping("do_login")
    public String doLogin(Model model, HttpServletResponse response, HttpServletRequest request, 
String mobile, String password) {log.info("User input data: mobile = " + mobile);if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
          model.addAttribute("msg", "請輸入手機號和密碼!");
            return INDEX;
}
       ......      try {UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(mobile, password);
	 //呼叫loadUserByUsername設定許可權資訊
Authentication authentication = authenticationManager.authenticate(authRequest); 
SecurityContextHolder.getContext().setAuthentication(authentication);log.info("恭喜使用者  " + mobile() + " 登入成功。");         ......
 response.sendRedirect('上一次訪問的頁面或welcome');
} catch (IOException ex) { log.warn("Error happened");} return INDEX;

四、登入頁login.vm

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登入頁</title>
......</head><body> <div> <p>登入</p> </div> <div> <div> <form id="loginForm" name="loginForm" action="/do_login" METHOD="post" > <h2><!-- a href="$!{base}/register">免費註冊</a -->商家登入</h2> <p id="y_lg_02">$!{msg}</p> <div><input type="text" placeholder="手機號" value="$!{mobile}"/></div> <div><input type="password" placeholder="" value="$!{password}"/></div> <p><input type="checkbox"/><em>自動登入</em></p> <input type="submit" value="立即登入"/> </form> </div> </div></body></html>

五、注意點:

用spring security預設的登入,登入頁的form的action="/manage/j_spring_security_check"這樣寫,預設會呼叫UsernamePasswordAuthenticationFilter中的驗證資訊。

自己寫的登入邏輯主要是要把驗證資訊寫入到spring security的上下文中,三行主要的程式碼如下:

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(mobile, password);
//呼叫loadUserByUsername設定許可權資訊         
Authentication authentication = authenticationManager.authenticate(authRequest); 
         SecurityContextHolder.getContext().setAuthentication(authentication);

對於自動登入的,需要把許可權也放入spring security上下文中,但是生成 UsernamePasswordAuthenticationToken物件需要密碼的明文,這裡我是把它放在快取中,是自動登入的直接從快取中取出來放進spring security上下文中。

final UserVo  Vo = getLoginUser(request);
       if (userVo != null && userVo.getId() != null) {
//登入資訊有效需要將許可權資訊放入SecurityContextHolder的上下文資訊中心,否則可能會造成迴圈重定向,伺服器報錯。
          Authentication authentication = (Authentication) request.getAttribute(CommonConstants.USER_AUTH);
          SecurityContextHolder.getContext().setAuthentication(authentication);
          // redirect to homepage
          return "redirect:/";
       }

自動登入開始把許可權也放入spring security上下文中,結果出現了迴圈重定向,是因為之前選擇了自動登入,則if條件為true,這時重定向的/login,又開始執行登入頁面的程式碼,造成了重定向死迴圈。

看到別人寫的一篇好文,有助於理解:http://blog.csdn.net/zy_cookie/article/details/49535413