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<import resource="/securityConfig.xml"/></beans>name="contentType" value="text/html;charset=UTF-8"></property> <property name="toolboxConfigLocation"> <value>/WEB-INF/toolbox.xml</value> </property> </bean>
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>
<!--<!– 阻止多端登入 –>--> <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> <!--<!– 對密碼進行MD5編碼 –>--> <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