系統開發中的使用者許可權的兩種流行實現。
使用者許可權,url攔截,這在系統開發是不可少的,也是很重要的一環。有時候,我們可能在頁面隱藏掉某些功能,但是,一旦我們直接通過url訪問,或者自己寫程式碼去訪問的時候,如果沒有許可權的控制,那麼我們的私有資料將暴露。知道了其重要性,那麼該怎麼去實現一個管理url方便,且安全的許可權系統呢。
其實現在市場上提供了很多第三方的安全框架,例如spring security,shrio。這些在工作中都是使用非常普遍的,又或者自己在spring mvc或者Struct框架中,使用攔截器去實現。又或者最原始的servlet中的攔截器。實現的方式有很多種,但是原理都是一樣的。
而且最最常見的無非得經過三個過濾:
1.訪問的url是否合法(帶參的檢查)
2.是否登入
3.是否對當前url擁有許可權
第一步可要可不要。但是是否登入,是否對當前url擁有許可權,則是必要的。
一般普遍且簡單的許可權系統,表設計:
1.使用者表
2.角色表
3.url資源表
4.使用者---角色表 (多對多)
5.角色---url資源表 (多對多)
現在不談實現,先談原理,走一遍流程:
使用者登入------根據輸入使用者id拿使用者整一條資料------如果拿不到,那麼返回錯誤說該使用者不存在,如果拿到了使用者資料,比對輸入的密碼,比對是否過期等使用者自定義的條件,比對正確,那麼成功登入。若不正確,則返回錯誤的資訊。若正確後,那麼可以在request中拿到使用者登入的日誌,寫進資料庫。將使用者資料儲存到session中(或者自己使用redis搭建的快取,這其實類似session)。成功登入以後,將會立刻跳轉到主頁url。此時此請求將要走許可權系統了。
首先得判斷是否登入了,如果session中有這個使用者的資料,那麼證明登入了,走過登入驗證後,將走許可權驗證:根據url去找角色,根據使用者去找角色,如果兩者有共同角色,那麼證明該使用者擁有該url的許可權。
整個流程就是這樣了。
如何用程式碼去實現呢?
在spring mvc框架下,我總結兩種流行的實現方式:
自己使用spring mvc提供的攔截器去實現:
首先springmvc配置檔案配置:
這個配置的意思是:對/androidpublic/*.do /Ad**/**/*.do的請求進行攔截,對/androidpublic/alogin.do /androidpublic/logout.do忽略掉。<!-- 登入攔截 ,許可權攔截--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/androidpublic/*.do"/> <mvc:mapping path="/Ad**/**/*.do"/> <mvc:exclude-mapping path="/androidpublic/alogin.do"/> <mvc:exclude-mapping path="/androidpublic/logout.do"/> <bean class="com.app.zf.itsm.android.publics.interceptor.AdLoginHandlerIntercep"/> </mvc:interceptor> </mvc:interceptors>
然後攔截器是:
com.app.zf.itsm.android.publics.interceptor.AdLoginHandlerIntercep
這是我一個專案裡安卓服務端的攔截器:
public class AdLoginHandlerIntercep extends HandlerInterceptorAdapter{
/**autor:cwy
* 00表示不是最初來源,01代表尚未登入。將登陸驗證和url許可權驗證寫在一起
* 說明:首先驗證登入,在登入的基礎上驗證來源是否正確,最後驗證是否有許可權
* 許可權驗證這裡有三種情況是不攔截的:1.url池裡一個url都沒有,2.url沒有錄入進資源管理或者,url沒有一個宿主,3。url錄入,且有宿主,但是另外的宿主沒有分配許可權
* 注意事項:一旦一個url錄入進資源管理,必須先給他一個宿主(系統管理員),否則,所有人都可以訪問這個url
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
PageData pd = new PageData();
pd=new PageData(request);
String token1=request.getParameter("TOKEN1");
String orgin=request.getParameter("ORIGIN");
String path=request.getRequestURI();
if(StringUtils.isNotEmpty(RedisPool.get(token1)))
{
if(StringUtils.isNotEmpty(request.getParameter("INFOR"))) //已登入,還需要驗證簽名(簽名存在)
{
String token2=RedisPool.get(token1); //獲得真正的token
String origin = MD5.md5(path+"?"+token2+orgin); //獲取token和url的MD5加密
if(!origin.equals(pd.get("INFOR"))) //對比簽名,防止url被人攔截後,模擬傳送請求
{
response.getWriter().print(InterceptorStatusConstant.NO_ORGIN); //不是最初來源
return false;
}
else
{
//已登入,簽名通過,還需要驗證是否對url有訪問許可權
String url=request.getServletPath();
//找到所有的url
List<String> allUrlList = MemeryCacheManager.getResourceRoleCache().getList("ALL_URL_LIST", String.class); // 從快取中讀取系統中所有url集合,進行對比
if (CollectionUtils.isEmpty(allUrlList)) {
SysResourceService resourceService = (SysResourceService) SpringContextUtil.getBean("sysresourceservice");
allUrlList = resourceService.findAllUrl();
}
if (CollectionUtils.isNotEmpty(allUrlList)) { //若資料庫存在url,且找不到url與請求的url相同,那麼將不攔截;如果有相同,在驗證是否url對應的角色與使用者擁有的角色是否相同,相同則不攔截,不相同則攔截,如果對應的url對應的角色為null,則不攔截。
for (String resURL : allUrlList) {
if (StringUtils.startsWith(url, resURL)) {
List<ConfigAttribute> atts = MemeryCacheManager.getResourceRoleCache().getList(resURL, ConfigAttribute.class); // 從快取讀取對應請求url,所包含的角色集合
if (CollectionUtils.isEmpty(atts)) {
SysResourceService resourceService = (SysResourceService) SpringContextUtil.getBean("sysresourceservice");
atts = resourceService.findConfigAttByURL(resURL); //通過url找url對應的角色,若找到了,執行下一步,找不到的話,代表url沒有宿主,所以這個url可以被所有的使用者訪問
}
if (CollectionUtils.isNotEmpty(atts)) {
Iterator<ConfigAttribute> ite = atts.iterator(); // 請求URL所對應的角色集合
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ((SecurityConfig) ca).getAttribute();
//從redis快取中拿使用者角色程式碼字串,並轉化成陣列
JSONArray relecodesJson=JSON.parseArray(RedisPublicInforUtil.getRolesCodes(request));
Object rolecodes[]= relecodesJson.toArray();
for (int i=0;i<rolecodes.length;i++) { // 使用者所擁有角色
if (StringUtils.equals(StringUtils.trim(needRole), StringUtils.trim(rolecodes[i].toString()))) {
return true;
}
}
}
response.getWriter().print(InterceptorStatusConstant.NO_PERMISION); //無對URL許可權
return false;
} else {
return true; //url沒有宿主(都可以訪問)
}
}
}
return true; //url沒配進url池
}
else
{
return true; //url池裡一個url都沒有
}
}
}
else
{
response.getWriter().print(InterceptorStatusConstant.NO_ORGIN);
return false; //請求為沒有帶標記
}
}
else
{
response.getWriter().print(InterceptorStatusConstant.NO_LOGIN); //沒有登入
return false;
}
}
}
第二種實現:使用spring security來實現
spring security 在市場上還是很受歡迎的。它的功能很強大,不僅僅是攔截,還提供單點登入,即後一個使用者登入會擠掉前一個登入的使用者(這是可配置的)。
來自當前正在開發的系統的一個spring security配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.1.xsd">
<!-- 不要攔截圖片等靜態資源,其中**代表可以跨越目錄,*不可以跨越目錄。 security=none可以理解為忽略-->
<!--<http pattern="/login_ace.jsp*" security="none"/>-->
<http pattern="/yinlinlogin.jsp*" security="none"/>
<http pattern="/common/**" security="none"/>
<!-- 不要過濾圖片等靜態資源,安卓的url不讓spring security來管理 -->
<http pattern="/**/*.jpg" security="none"/>
<http pattern="/**/*.png" security="none"/>
<http pattern="/**/*.gif" security="none"/>
<http pattern="/**/*.css" security="none"/>
<http pattern="/**/*.js" security="none"/>
<http pattern="/**/*.swf" security="none"/>
<http pattern="/j_captcha.do*" security="none"/>
<http pattern="/androidpublic/*.do" security="none"/>
<http pattern="/Adcmdb/**/*.do" security="none"/>
<!-- auto-config = true 則使用from-login. 如果不使用該屬性 則預設為http-basic(沒有session).
access-denied-page:出錯後跳轉到的錯誤頁面; -->
<http auto-config="true" access-denied-page="/common/403_denied.jsp">
<!-- login-page:預設指定的登入頁面. authentication-failure-url:驗證失敗跳轉頁面. default-target-url:成功登陸後跳轉頁面 -->
<form-login login-page="/yinlinlogin.jsp?login" authentication-failure-url="/yinlinlogin.jsp?failure" default-target-url="/index/yinlinindex.do" />
<!-- logout-success-url:成功登出後跳轉到的頁面; -->
<logout logout-success-url="/yinlinlogin.jsp?logout" />
<http-basic />
<!-- session-management是針對session的管理. 這裡可以不配置. 如有需求可以配置. -->
<!-- id登陸唯一. 後登陸的賬號會擠掉第一次登陸的賬號 error-if-maximum-exceeded="true" 禁止2次登陸;
session-fixation-protection="none" 防止偽造sessionid攻擊. 使用者登入成功後會銷燬使用者當前的session.
建立新的session,並把使用者資訊複製到新session中. expired-url : session過期跳轉 -->
<session-management invalid-session-url="/yinlinlogin.jsp?invalid">
<concurrency-control expired-url="/yinlinlogin.jsp?expired" />
</session-management>
<!-- 增加一個filter,不能修改預設的filter了,這個filter位於FILTER_SECURITY_INTERCEPTOR之前 -->
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="exFilter" />
</http>
<!-- 一個自定義的filter,必須包含authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,我們的所有控制將在這三個類中實現,解釋詳見具體配置 -->
<beans:bean id="exFilter" class="com.app.core.security3.MyFilterSecurityInterceptor">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="accessDecisionManager" ref="accessDecisionManager" />
<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>
<!-- 許可權管理操作 -->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailService">
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<!-- 使用者資訊 -->
<beans:bean id="userDetailService" class="com.app.core.security3.MyUserDetailService">
<beans:property name="sysUserService" ref="sysuserservice"/>
<beans:property name="sysOrganizationService" ref="sysorganizationservice"/>
<beans:property name="sysUserLoginLogService" ref="sysUserLoginLogService"/>
</beans:bean>
<!-- 訪問決策器,決定某個使用者具有的角色,是否有足夠的許可權去訪問某個資源 -->
<beans:bean id="accessDecisionManager" class="com.app.core.security3.MyAccessDecisionManager" />
<!-- 資源源資料定義,即定義某一資源可以被哪些角色訪問 -->
<beans:bean id="securityMetadataSource" class="com.app.core.security3.MyInvocationSecurityMetadataSource" />
<!-- 國際化 -->
<beans:bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basenames">
<beans:list>
<beans:value>org.springframework.security.messages</beans:value>
</beans:list>
</beans:property>
</beans:bean>
</beans:beans>
對於登入,spring security也幫我們做了,驗證碼是用了谷歌的
<br><div class="login_title">系統登入</div>
<div class="login_list" title="Register">
<form id="loginForm" action="${pageContext.request.contextPath}/j_spring_security_check" method="post">
<!-- 賬號 -->
<div class="login_list_li">
<div class="login_list_li_icon icon35 icon35_01"></div>
<input class="easyui-textbox" id="j_username" name="j_username" data-options="prompt:' 輸入賬號...'">
</div>
<!-- 密碼 -->
<div class="login_list_li">
<div class="login_list_li_icon icon35 icon35_02"></div>
<input name="j_password" type="password" class="easyui-textbox" data-options="prompt:' 輸入密碼...'">
<i class="ace-icon fa fa-user"></i>
</div>
<!-- 驗證碼 -->
<div class="login_list_li login_list_li_min clearfix">
<div class="login_list_li_icon icon35 icon35_03"></div>
<input id="j_kaptcha" name="j_kaptcha" class="easyui-textbox" data-options="prompt:' 驗證碼...'">
<div class="Fl login_list_li_Msg">
<label class="inline">
<img src="${pageContext.request.contextPath}/j_captcha.do" style="cursor: pointer;width: 75px;height: 24px;" id="kaptchaImage" title="重新獲取" onclick="reloadKaptcha()"/>
</label>
</div>
</div>
<font color="red">${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}</font>
<!-- <div id="showMsg"></div>-->
<!-- 登入按鈕 -->
<input type="submit" class="easyui-linkbutton login_ok" style="width:100%;height:32px" value="登 陸"/>
</form>
</div>
驗證碼:
<!-- Kaptcha驗證碼 -->
<bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
<property name="config">
<bean class="com.google.code.kaptcha.util.Config">
<constructor-arg>
<props>
<prop key="kaptcha.border">yes</prop>
<prop key="kaptcha.border.color">211,211,211</prop>
<prop key="kaptcha.textproducer.font.color">47,79,79</prop>
<prop key="kaptcha.textproducer.font.size">23</prop>
<prop key="kaptcha.image.width">75</prop>
<prop key="kaptcha.image.height">24</prop>
<prop key="kaptcha.textproducer.char.string">1234567890</prop>
<prop key="kaptcha.textproducer.char.length">4</prop>
<prop key="kaptcha.textproducer.font.names">新宋體</prop>
<prop key="kaptcha.background.clear.from">white</prop>
<prop key="kaptcha.noise.impl">com.google.code.kaptcha.impl.NoNoise</prop>
<prop key="kaptcha.obscurificator.impl">com.google.code.kaptcha.impl.ShadowGimpy</prop>
</props>
</constructor-arg>
</bean>
</property>
</bean>
點選登入提交:會走spring security框架:到達:UserDetailsService;一般情況下,我們需要實現它,重寫
@Override
public UserDetails loadUserByUsername(String arg0)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
return null;
}
arg0代表的其實就是賬號。這個方法裡面需要放置的邏輯是:根據賬號拿使用者資訊,包括基本資訊和額外需要用到的資訊,例如使用者擁有的角色,使用者所在的資料域等等,然後將其返回。框架會自動幫助我們去判斷密碼,賬號是否到期等等。
一個實現例項:
使用者實體類需要實現框架的UserDetails介面:這個介面是
public class SysUser extends BaseEntity implements UserDetails, Serializable {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
}
一個實現它的例項:
public class SysUser extends BaseEntity implements UserDetails, Serializable {
private static final String mgrAccount = "admin";//系統管理員
private static final long serialVersionUID = 1L;
private String username; // 名稱
private String sex; // 性別
private String loginId; // 登陸賬號
private String password; // 密碼
private String enName; // 英文名
private String employeeId; // 員工ID
private String post; // 崗位
private String duty; // 職務
private String email; // 郵箱
private String qq;
private String mobile; // 手機
private String officePhone; // 辦公電話
private String dutyDesc; // 職責
private String headImage; // 頭像地址
private Date birthday; // 生日
private Date joinDate; // 入職日期
private Date expiredDate; // 合同到期日期
private String stateFlag; // 狀態 1:在職,2:離職,3:待定
private String showState; // 隱藏狀態 y:顯示,h:隱藏
private String deleteFlag; // n:未刪除
private Integer orderBy; // 排序
private String remark; // 備註
private String companyId; // 公司ID
private String phonecode; //手機唯一碼
@Transient
public String getPhonecode() {
return phonecode;
}
@Transient
public void setPhonecode(String phonecode) {
this.phonecode = phonecode;
}
// 臨時
private String birthdayFmt; // 格式化
private String joinDateFmt; // 格式化
private String expiredDateFmt; // 格式化
private String expiredState; // 過期狀態
private String companyName; // 公司名稱
private String companyCode; // 公司編碼
private String companyType; // 公司型別
private String orgId; // 組織ID
private String orgName; // 組織名稱
private String parentOrgIds; // 父輩組織ID
private String parentOrgNames; // 父輩組織
private String virtualOrgId; // 虛擬組織ID
private String virtualOrgName; // 虛擬組織名稱
private String postName; // 崗位名稱
private String dutyName; // 職務名稱
private String roleId; // 角色ID
private boolean isManage; // 是否系統管理機構,屬於該組織下的人員擁有檢視全部資料許可權
private List<SysRole> roles; // 擁有的角色集合
private List<SimpleGrantedAuthority> authlist = new ArrayList<SimpleGrantedAuthority>(); // spring_security驗證角色編碼
List<SysCompany> companyList; // 關聯公司或服務商
List<SysUserGroup> userGroupList; // 所屬班組
private Date dutywatchadate; //排班的值班日期
public SysUser() {
}
public SysUser(String resourceid, String username) {
super();
setResourceid(resourceid);
this.username = username;
}
@Column(name = "USERNAME", length = 50)
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
@Column(name = "SEX", length = 50)
public String getSex() {
return this.sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Column(name = "LOGIN_ID", length = 50)
public String getLoginId() {
return this.loginId;
}
public void setLoginId(String loginId) {
this.loginId = loginId;
}
@Column(name = "PASSWORD", length = 32)
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name = "EN_NAME", length = 50)
public String getEnName() {
return this.enName;
}
public void setEnName(String enName) {
this.enName = enName;
}
@Column(name = "EMPLOYEE_ID", length = 50)
public String getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
@Column(name = "POST", length = 50)
public String getPost() {
return this.post;
}
public void setPost(String post) {
this.post = post;
}
@Column(name = "DUTY", length = 50)
public String getDuty() {
return duty;
}
public void setDuty(String duty) {
this.duty = duty;
}
@Column(name = "EMAIL", length = 100)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name = "QQ", length = 50)
public String getQq() {
return this.qq;
}
public void setQq(String qq) {
this.qq = qq;
}
@Column(name = "MOBILE", length = 100)
public String getMobile() {
return this.mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
@Column(name = "OFFICE_PHONE", length = 100)
public String getOfficePhone() {
return this.officePhone;
}
public void setOfficePhone(String officePhone) {
this.officePhone = officePhone;
}
@Column(name = "DUTY_DESC", length = 650)
public String getDutyDesc() {
return this.dutyDesc;
}
public void setDutyDesc(String dutyDesc) {
this.dutyDesc = dutyDesc;
}
@Column(name = "HEAD_IMAGE", length = 150)
public String getHeadImage() {
return this.headImage;
}
public void setHeadImage(String headImage) {
this.headImage = headImage;
}
@Temporal(TemporalType.DATE)
@Column(name = "BIRTHDAY")
public Date getBirthday() {
return this.birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
birthdayFmt = birthday != null ? DateTimeUtil.formatDate(birthday) : "";
}
@Temporal(TemporalType.DATE)
@Column(name = "JOIN_DATE")
public Date getJoinDate() {
return this.joinDate;
}
public void setJoinDate(Date joinDate) {
this.joinDate = joinDate;
joinDateFmt = joinDate != null ? DateTimeUtil.formatDate(joinDate) : "";
}
@Temporal(TemporalType.DATE)
@Column(name = "EXPIRED_DATE")
public Date getExpiredDate() {
return expiredDate;
}
private boolean expiredBool = true; // 過期標誌:預設有效
public void setExpiredDate(Date expiredDate) {
this.expiredDate = expiredDate;
expiredDateFmt = expiredDate != null ? DateTimeUtil.formatDate(expiredDate) : "";
if (null == expiredDate) {
setExpiredState("長期");
} else {
if (DateTimeUtil.addDays(expiredDate, 1).compareTo(new Date()) < 0) { // 合同到期時間小與當前時間,賬號過期
setExpiredState("<font color='red'>過期</font>");
expiredBool = false;
} else {
setExpiredState("有效");
}
}
}
@Column(name = "STATE_FLAG", length = 1)
public String getStateFlag() {
return stateFlag;
}
public void setStateFlag(String stateFlag) {
this.stateFlag = stateFlag;
}
@Column(name = "SHOW_STATE", length = 1)
public String getShowState() {
return showState;
}
public void setShowState(String showState) {
this.showState = showState;
}
@Column(name = "DELETE_FLAG", length = 1)
public String getDeleteFlag() {
return deleteFlag;
}
public void setDeleteFlag(String deleteFlag) {
this.deleteFlag = deleteFlag;
}
@Column(name = "ORDER_BY")
public Integer getOrderBy() {
return this.orderBy;
}
public void setOrderBy(Integer orderBy) {
this.orderBy = orderBy;
}
@Column(name = "REMARK", length = 500)
public String getRemark() {
return this.remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Column(name = "COMPANY_ID")
public String getCompanyId() {
return companyId;
}
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
/**
* 是否系統管理機構,屬於該組織下的人員擁有檢視全部資料許可權
*
* @return true:是(檢視全部資料)、false:否(只能檢視本公司資料)
*/
@Transient
public boolean isManage() {
if(isManage && mgrAccount.equals(this.getLoginId())){
return true;
}else{
return false;
}
}
public void setManage(boolean isManage) {
this.isManage = isManage;
}
@Transient
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
// 組裝使用者擁有角色成spring security驗證
if (roles != null && roles.size() > 0) {
for (SysRole role : roles) {
authlist.add(new SimpleGrantedAuthority(role.getRoleCode()));
}
}
}
/**
* 過期標誌
*/
@Transient
public boolean isAccountNonExpired() {
return expiredBool;
}
/**
* 鎖定標誌,在職狀態才能進入系統
*/
@Transient
public boolean isAccountNonLocked() {
return StringUtils.equalsIgnoreCase(stateFlag, "1") ? true : false;
}
@Transient
public boolean isCredentialsNonExpired() {
return true;
}
@Transient
public boolean isEnabled() {
return true;
}
// 重寫equals與hashCode,判斷使用者相等
public boolean equals(Object rhs) {
if (!(rhs instanceof SysUser) || (rhs == null)) {
return false;
}
SysUser user = (SysUser) rhs;
return (this.getPassword().equals(user.getPassword()) && this.getUsername().equals(user.getUsername()) && (this.isAccountNonExpired() == user.isAccountNonExpired())
&& (this.isAccountNonLocked() == user.isAccountNonLocked()) && (this.isCredentialsNonExpired() == user.isCredentialsNonExpired()) && (this.isEnabled() == user.isEnabled()));
}
public int hashCode() {
int code = 9792;
for (GrantedAuthority authority : getAuthorities()) {
code = code * (authority.hashCode() % 7);
}
if (this.getPassword() != null) {
code = code * (this.getPassword().hashCode() % 7);
}
if (this.getUsername() != null) {
code = code * (this.getUsername().hashCode() % 7);
}
if (this.isAccountNonExpired()) {
code = code * -2;
}
if (this.isAccountNonLocked()) {
code = code * -3;
}
if (this.isCredentialsNonExpired()) {
code = code * -5;
}
if (this.isEnabled()) {
code = code * -7;
}
return code;
}
// 組裝使用者擁有角色成spring security驗證
@Transient
public Collection<? extends GrantedAuthority> getAuthorities() {
return authlist;
}
@Transient
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
@Transient
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
@Transient
public String getOrgName() {
return orgName;
}
public void setOrgName(String orgName) {
this.orgName = orgName;
}
@Transient
public String getParentOrgIds() {
return parentOrgIds;
}
public void setParentOrgIds(String parentOrgIds) {
this.parentOrgIds = parentOrgIds;
}
@Transient
public String getParentOrgNames() {
return parentOrgNames;
}
public void setParentOrgNames(String parentOrgNames) {
this.parentOrgNames = parentOrgNames;
}
@Transient
public String getVirtualOrgId() {
return virtualOrgId;
}
public void setVirtualOrgId(String virtualOrgId) {
this.virtualOrgId = virtualOrgId;
}
@Transient
public String getVirtualOrgName() {
return virtualOrgName;
}
public void setVirtualOrgName(String virtualOrgName) {
this.virtualOrgName = virtualOrgName;
}
@Transient
public String getPostName() {
return postName;
}
public void setPostName(String postName) {
this.postName = postName;
}
@Transient
public String getBirthdayFmt() {
return birthdayFmt;
}
public void setBirthdayFmt(String birthdayFmt) {
this.birthdayFmt = birthdayFmt;
}
@Transient
public String getJoinDateFmt() {
return joinDateFmt;
}
public void setJoinDateFmt(String joinDateFmt) {
this.joinDateFmt = joinDateFmt;
}
@Transient
public String getExpiredDateFmt() {
return expiredDateFmt;
}
public void setExpiredDateFmt(String expiredDateFmt) {
this.expiredDateFmt = expiredDateFmt;
}
@Transient
public String getExpiredState() {
return expiredState;
}
public void setExpiredState(String expiredState) {
this.expiredState = expiredState;
}
@Transient
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
@Transient
public String getCompanyCode() {
return companyCode;
}
public void setCompanyCode(String companyCode) {
this.companyCode = companyCode;
String manageOrg = PropertiesUtil.get("SYS_MANAGE"); // 系統管理機構編碼
if (StringUtils.equals(companyCode, manageOrg))
setManage(true);
}
@Transient
public String getCompanyType() {
return companyType;
}
public void setCompanyType(String companyType) {
this.companyType = companyType;
}
@Transient
public List<SysCompany> getCompanyList() {
return companyList;
}
public void setCompanyList(List<SysCompany> companyList) {
this.companyList = companyList;
}
@Transient
public List<SysUserGroup> getUserGroupList() {
return userGroupList;
}
public void setUserGroupList(List<SysUserGroup> userGroupList) {
this.userGroupList = userGroupList;
}
@Transient
@Temporal(TemporalType.DATE)
public Date getDutywatchadate() {
return dutywatchadate;
}
public void setDutywatchadate(Date dutywatchadate) {
this.dutywatchadate = dutywatchadate;
}
@Transient
public String getDutyName() {
return dutyName;
}
public void setDutyName(String dutyName) {
this.dutyName = dutyName;
}
}
使用者登入將走到這裡來
public class MyUserDetailService implements UserDetailsService {
private static Logger logger = LogManager.getLogger(MyUserDetailService.class);
// 注入服務
SysUserService sysUserService;
SysOrganizationService sysOrganizationService;
SysUserLoginLogService sysUserLoginLogService;
@Autowired
SysUserGroupService sysUserGroupService;
public SysUser loadUserByUsername(String loginId) throws UsernameNotFoundException, DataAccessException {
SysUser loginUser = new SysUser(); // 登入使用者
if (StringUtils.isNotEmpty(loginId)) {
logger.info(loginId + " LOGIN...");
loginUser = sysUserService.findUserByLoginId(loginId); // 通過loginId查詢使用者
if (null != loginUser) {
loginUser.setPostName(DictionaryELTag.getTextByCompanyId("D_Post", loginUser.getPost(),loginUser.getCompanyId()));
// 查詢使用者擁有角色
List<SysRole> roles = sysUserService.findSysRoleByUserId(loginUser.getResourceid());
if (CollectionUtils.isNotEmpty(roles)){
loginUser.setRoles(roles);
}
if (StringUtils.equals(loginUser.getCompanyType(), "1")) { // 1:公司
List<SysCompany> companyList = sysOrganizationService.findCompanyByType("2", loginUser.getCompanyId());
loginUser.setCompanyList(companyList);
} else if (StringUtils.equals(loginUser.getCompanyType(), "2")) { // 2:服務商
List<SysCompany> companyList = sysOrganizationService.findCompanyByType("1", loginUser.getCompanyId());
loginUser.setCompanyList(companyList);
}
//查詢所在班組(list)
List<SysUserGroup> groupList = sysUserGroupService.getUserGroupByUserid(loginUser.getResourceid());
if (CollectionUtils.isNotEmpty(groupList)){
loginUser.setUserGroupList(groupList);
}
} else {
logger.info("使用者名稱(" + loginId + ")不存在...");
throw new UsernameNotFoundException("使用者名稱(" + loginId + ")不存在...");
}
} else {
throw new UsernameNotFoundException("使用者名稱不能為空...");
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String agent = request.getHeader("User-Agent").toUpperCase(); // 瀏覽器型別
String screen_height = request.getParameter("screen_height"); // 解析度高
String screen_width = request.getParameter("screen_width"); // 解析度寬
String ip = getIpAddr(request);
SysUserLoginLog loginLog = new SysUserLoginLog(loginUser.getResourceid(), loginUser.getUsername(), ip, agent, screen_height, screen_width, loginUser.getCompanyId());
sysUserLoginLogService.save(loginLog); // 儲存登入日誌
logger.info(loginId + " SUCCESS LOGIN...IP:" + ip);
return loginUser;
}
/**
* 通過HttpServletRequest返回IP地址
*
* @param request
* HttpServletRequest
* @return ip String
*/
public String getIpAddr(HttpServletRequest request) throws RuntimeException {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip == null && ("0:0:0:0:0:0:0:1").equals(ip))
ip = "127.0.0.1";
return ip;
}
public SysUserService getSysUserService() {
return sysUserService;
}
public void setSysUserService(SysUserService sysUserService) {
this.sysUserService = sysUserService;
}
public SysOrganizationService getSysOrganizationService() {
return sysOrganizationService;
}
public void setSysOrganizationService(SysOrganizationService sysOrganizationService) {
this.sysOrganizationService = sysOrganizationService;
}
public SysUserLoginLogService getSysUserLoginLogService() {
return sysUserLoginLogService;
}
public void setSysUserLoginLogService(SysUserLoginLogService sysUserLoginLogService) {
this.sysUserLoginLogService = sysUserLoginLogService;
}
}
返回一個使用者,由框架給我們做驗證密碼,驗證是否過期的動作。
登入成功後訪問一個url:
走攔截器:
/**
* TODO:1、攔截器
*/
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void destroy() {
}
public void init(FilterConfig arg0) throws ServletException {
}
}
然後根據請求的url去找到該url對應的角色
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static Logger logger = LogManager.getLogger(MyInvocationSecurityMetadataSource.class);
public MyInvocationSecurityMetadataSource() {
logger.debug("===============================初始化=================================");
}
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String url = ((FilterInvocation) object).getRequestUrl(); // 請求地址
List<String> allUrlList = MemeryCacheManager.getResourceRoleCache().getList("ALL_URL_LIST", String.class); // 從快取中讀取系統中所有url集合,進行對比
if (CollectionUtils.isEmpty(allUrlList)) {
SysResourceService resourceService = (SysResourceService) SpringContextUtil.getBean("sysresourceservice");
allUrlList = resourceService.findAllUrl();
}
logger.info((SpringContextUtil.getUser() != null ? "USER:" + SpringContextUtil.getUser().getUsername():"使用者沒登陸") + " ACCESS_URL:" + url);
if (CollectionUtils.isNotEmpty(allUrlList)) {
for (String resURL : allUrlList) {
if (StringUtils.startsWith(url, resURL)) {
List<ConfigAttribute> atts = MemeryCacheManager.getResourceRoleCache().getList(resURL, ConfigAttribute.class); // 從快取讀取對應請求url,所包含的角色集合
if (CollectionUtils.isEmpty(atts)) {
SysResourceService resourceService = (SysResourceService) SpringContextUtil.getBean("sysresourceservice");
atts = resourceService.findConfigAttByURL(resURL);
}
if (CollectionUtils.isNotEmpty(atts)) {
return atts;
} else {
return null;
}
}
} // end for
}
return null;
}
public boolean supports(Class<?> clazz) {
return true;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
若url對應的就是為null,則所有人都可以訪問,否則返回角色的集合,到第三階段:許可權和是否登入的攔截:
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 根據使用者的角色與URL擁有的角色對比,以確認是否有許可權 authentication:訪問者所擁有的角色 object:請求URL
* configAttributes:請求資源所擁有的角色
* 說明:如果在資源管理那邊新增了一個url,那麼務必給這個url分配一個宿主,否則,所有人(對這個資源無許可權的使用者)也可以訪問
*/
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if (configAttributes == null) //請求資源擁有的角色為null時,所有人都可以去訪問這個資源
{
return;
}
// HttpServletRequest request = invocation.getHttpRequest();
Object obj = authentication.getPrincipal();
if (obj.toString().equals("anonymousUser")) {
FilterInvocation invocation = (FilterInvocation) object;
String fullUrl = invocation.getFullRequestUrl(); // 外部訪問全地址
String basePath = PropertiesUtil.get("SYS_BASE_PATH");
if (StringUtils.startsWithIgnoreCase(fullUrl, basePath)) // 外部訪問
return;
}
Iterator<ConfigAttribute> ite = configAttributes.iterator(); // 請求URL所對應的角色集合
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ((SecurityConfig) ca).getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) { // 使用者所擁有角色
if (StringUtils.equals(StringUtils.trim(needRole), StringUtils.trim(ga.getAuthority()))) {
return;
}
}
}
throw new AccessDeniedException("請求失敗,請檢查您的許可權...");
}
public boolean supports(ConfigAttribute configattribute) {
return true;
}
public boolean supports(Class<?> class1) {
return true;
}
}
有返回的,代表走通了,否則就是將丟擲spring security提供的AccessDeniedException異常
本文完畢。
相關推薦
系統開發中的使用者許可權的兩種流行實現。
使用者許可權,url攔截,這在系統開發是不可少的,也是很重要的一環。有時候,我們可能在頁面隱藏掉某些功能,但是,一旦我們直接通過url訪問,或者自己寫程式碼去訪問的時候,如果沒有許可權的控制,那麼我們的私有資料將暴露。知道了其重要性,那麼該怎麼去實現一個管理url方便,且安
設計一個程序,有一個虛擬存儲區和內存工作區,實現下述三種算法中的任意兩種,計算訪問命中率(命中率=1-頁面失效次數/頁地址流長度)。附加要求:能夠顯示頁面置換過程。算法包括:先進先出的算法(FIFO)、最少使用算法(LFU)、最近未使用算法(NUR)
== oat 程序 表示 隊列 ini ++ 等待 進程 第一部分。。。 #include <cstdlib>#include<conio.h> #include<stdio.h>#include<stdlib.h>#incl
【Spark】篇---Spark中yarn模式兩種提交任務方式
方式 div -s and clas client 命令 yarn 模式 一、前述 Spark可以和Yarn整合,將Application提交到Yarn上運行,和StandAlone提交模式一樣,Yarn也有兩種提交任務的方式。 二、具體 1、yarn
mybatis的sql中字段兩種映射(映射到實體)方式
tina 文件中 gpo entity 實體 body 映射 from tap mybatis的xml配置文件中,字段映射的兩種方式: 1.resultMap標簽中將數據庫的字段與實體類中的字段對應: <resultMap id="BaseResultMap" ty
python中的字典兩種遍歷方式
方法 clas items color 兩種方法 pan code bsp for dic = {"k1":"v1", "k2":"v2"} for k in dic: print(dic[K]) for k, v in dic.items(): pri
jsp中include 的兩種用法
1.兩種用法 靜態include: <%@ inlcude file =”header.jsp” %> 此時引入的是靜態的jsp檔案,它將引入的jsp中的原始碼原封不動地附加到當前檔案中,所以在jsp程式中使用這個指令的時候file裡面的值(即要匯入的檔案)不能帶多餘
myabtis入門級03——mybatis開發dao層的兩種方法
一、先來談談SqlSession的適用範圍 通過SqlSessionFactoryBuilder建立會話工廠SqlSessionFactory。 將SqlSessionFactoryBuilder當成一個工具類使用即可,不需要使用單例管理SqlSessionFactoryBu
java中代理,靜態代理,動態代理以及spring aop代理方式,實現原理統一彙總 Spring中AOP的兩種代理方式(Java動態代理和CGLIB代理)
若代理類在程式執行前就已經存在,那麼這種代理方式被成為 靜態代理 ,這種情況下的代理類通常都是我們在Java程式碼中定義的。 通常情況下, 靜態代理中的代理類和委託類會實現同一介面或是派生自相同的父類。 一、概述1. 什麼是代理我們大家都知道微商代理,簡單地說就是代替廠家賣商品,廠家“委託”代理為
Android第一天---開發中常用的幾種佈局
第一種:LinearLayout:線性佈局 線性佈局是按照處置或者水平進行排布的,預設是水平 屬性:orientation:用來指定當前的線性佈局的排布方向。 wrap_content:包裹內容 match_parent:匹配父類 margin:外邊距 padding
WPF開發中常用的幾種佈局元素
Grid:網格。可以自定義行和列並通過行列的數量、行高和行寬來調整控制元件的佈局,類似於HTML中的Table。 StackPanel:棧式模板。可將包含的元素在豎直或水平方向上排成一條直線,當移除一個元素後,後面的元素會自動向前移動以填充空缺。 Canvas:畫布。內部元素可以使用以畫素為單位的絕對座標
微服務架構中zuul的兩種隔離機制實驗
ZuulException REJECTED_SEMAPHORE_EXECUTION 是一個最近在效能測試中經常遇到的異常。查詢資料發現是因為zuul預設每個路由直接用訊號量做隔離,並且預設值是100,也就是當一個路由請求的訊號量高於100那麼就拒絕服務了,返回500。 訊號量隔離 既然預設值太小,那麼就
車機系統開發中的經驗總結
1、ACC OFF代表的是車輛熄火,ACC ON代表的是車輛打火;LOCK ON代表的是開啟車門,LOCK OFF代表的是關閉車門;B+代表的是車上的電源,連線B+就是把車前面的電瓶的電源連上,掉B+就是把電瓶的電源拔了。 2、主機的執行狀態有多種,需要注意區分。 息屏狀態:MCU只是單
Spark中yarn模式兩種提交任務方式
轉自:https://www.cnblogs.com/LHWorldBlog/p/8414342.html 一、前述 Spark可以和Yarn整合,將Application提交到Yarn上執行,和StandAlone提交模式一樣,Yarn也有兩種提交任務的方式。 二、具體  
redis原始碼分析與思考(三)——字典中鍵的兩種hash演算法
在Redis字典中,得到鍵的hash值顯得尤為重要,因為這個不僅關乎到是否字典能做到負載均衡,以及在效能上優勢是否突出,一個良好的hash演算法在此時就能發揮出巨大的作用。而一個良好的has
spring中AOP的兩種實現方式
1.方法一:註解實現 介面類 public interface User { public void work(); } 具體實現類 public class IUser implements User { public void work() {
【Spark篇】---Spark中yarn模式兩種提交任務方式
一、前述Spark可以和Yarn整合,將Application提交到Yarn上執行,和StandAlone提交模式一樣,Yarn也有兩種提交任務的方式。二、具體 1、yarn-client提交任務方式配置 在client節點配置中spark
MyBatis開發Dao層的兩種方式(Mapper動態代理方式)
MyBatis開發原始Dao層請閱讀我的上一篇部落格:MyBatis開發Dao層的兩種方式(原始Dao層開發) 接上一篇部落格繼續介紹MyBatis開發Dao層的第二種方式:Mapper動態代理方式 Mapper介面開發方法只需要程式設計師編寫Mapper介面(相當於Dao介面),由Mybat
Java中BorderLayout佈局管理器的兩種排列實現方式
java中Frame類預設的佈局管理器為BorderLayout,其主要是將Frame視窗分為東西南北中五個區域,每個區域僅限於放一個元件,如加入多個,前免得會被覆蓋,解決方法為:可以在一個區域中加入文字框Panel,在Panel中可以加入其他的元件。如果不指定加入的
C++中#include的兩種方式
一、#include< > #include< > 引用的是編譯器的類庫路徑裡面的標頭檔案。 假如你編譯器定義的自帶標頭檔案引用在 C:\Keil\c51\INC\ 下面,則 #include<stdio.h> 引用的就是 C:
jsp中include的兩種用法
<%@ include file=” ”%> include指令 <jsp:include page=” ” flush=”true”/> include動作 主要有兩方面的不同: 1.執行時間上 <%@ include file=” ”