SSM集成Spring Security

本文檔主要記錄的是SSM框架集成Spring Security來做賬戶登錄操作,已經集成了MyBatis,省略了基本的操作

實體User

首先需要先定義用戶實體User的dto


public class User{
    @Id//標識主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY) //自增長策略
    private Long id;

    private String email;

    private String password;

    private String phone;

    private String nickName;

    private String state;

    private String imgUrl;

    private String enable;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

   public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getPassword() {
        return password;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone == null ? null : phone.trim();
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName == null ? null : nickName.trim();
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state == null ? null : state.trim();
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl == null ? null : imgUrl.trim();
    }

    public String getEnable() {
        return enable;
    }

    public void setEnable(String enable) {
        this.enable = enable == null ? null : enable.trim();
    }

    }

實現UserDetails的接口

框架需要UserDetails類型的實體,所以我們要將User實現UserDetails的接口,然後重寫他的所有方法

賬戶信息需要包含角色信息,因此添加了一個Role屬性

public class User implements UserDetails {
    @Id//標識主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY) //自增長策略
    private Long id;

    private String email;

    private String password;

    private String phone;

    private String nickName;

    private String state;

    private String imgUrl;

    private String enable;

    @Transient
    protected List<Role> roles;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email == null ? null : email.trim();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(roles == null || roles.size()<=0){
            return null;
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        for(Role r:roles){
            authorities.add(new SimpleGrantedAuthority(r.getRoleValue()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        if(StringUtils.isNotBlank(state) && "1".equals(state) && StringUtils.isNotBlank(enable) && "1".equals(enable)){
            return true;
        }
        return false;
    }

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone == null ? null : phone.trim();
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName == null ? null : nickName.trim();
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state == null ? null : state.trim();
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl == null ? null : imgUrl.trim();
    }

    public String getEnable() {
        return enable;
    }

    public void setEnable(String enable) {
        this.enable = enable == null ? null : enable.trim();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return getEmail().equals(((User)obj).getEmail())||getUsername().equals(((User)obj).getUsername());
        }
        return false;
    }
    @Override
    public int hashCode() {
        return getUsername().hashCode();
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", phone='" + phone + '\'' +
                ", nickName='" + nickName + '\'' +
                ", state='" + state + '\'' +
                ", imgUrl='" + imgUrl + '\'' +
                ", enable='" + enable + '\'' +
                ", roles=" + roles +
                '}';
    }
}
public SimpleGrantedAuthority(String role) {
    Assert.hasText(role, "A granted authority textual representation is required");
    this.role = role;
}

SimpleGrantedAuthority的構造方法是String類型,

getAuthorities 方法是獲取用戶角色信息的方法,用於授權。不同的角色可以擁有不同的權限。

爲了區分是否是同一個用戶,重寫 equals 和 hashCode 方法。

重寫toString是爲了輸出信息

實現 UserDetailsService 接口

Spring Security 提供的獲取用戶信息的方式不滿足我們的需求,所以我們自己制定獲取用戶信息的方式

我們不止是獲取用戶信息還需要獲取角色信息

定義Role的實體類


public class Role {
    @Id//標識主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY) //自增長策略
    private Long id;

    private String roleName;

    private String roleValue;

    private String roleMatcher;

    private String enabled;

    private String remark;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName == null ? null : roleName.trim();
    }

    public String getRoleValue() {
        return roleValue;
    }

    public void setRoleValue(String roleValue) {
        this.roleValue = roleValue == null ? null : roleValue.trim();
    }

    public String getRoleMatcher() {
        return roleMatcher;
    }

    public void setRoleMatcher(String roleMatcher) {
        this.roleMatcher = roleMatcher == null ? null : roleMatcher.trim();
    }

    public String getEnabled() {
        return enabled;
    }

    public void setEnabled(String enabled) {
        this.enabled = enabled == null ? null : enabled.trim();
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark == null ? null : remark.trim();
    }
}

因爲獲取的不只是用戶信息還包含了角色信心,因此需要根據員工Code查詢角色數據

在RoleService中添加對應的方法:

/**
     * 根據用戶id查詢所有角色
     * @param uid
     * @return
     */
    List<Role> findByUid(Long uid);

在對應的Mapper中添加對應的方法
 

/**
     * 根據用戶id查詢角色信息
     * @param uid
     * @return
     */
    List<Role> findByUid(@Param("uid")Long uid);
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="yuan.boke.www.dao.RoleMapper">
    <select id="findByUid"  resultMap="roleListMap">
      select id,role_name,role_value,enabled from role where id in(select r.r_id from user u, role_user r where u.id = r.u_id and r.u_id = #{uid}) and enabled =1
  </select>

    <resultMap type="yuan.boke.www.entity.Role" id="roleListMap">
        <id property="id" column="id" />
        <result property="roleName" column="role_name" />
        <result property="roleValue" column="role_value" />
        <result property="enabled" column="enabled" />
    </resultMap>
</mapper>

在實現類中實現其方法RoleServiceImpl 

public class RoleServiceImpl implements RoleService {
    @Autowired
    RoleMapper roleMapper;
    @Override
    public List<Role> findByUid(Long uid) {
        return roleMapper.findByUid(uid);
    }
}

實現UserDetailsService,創建類AccountDetailsService,重寫賬戶查詢返回UserDetails的實體


public class AccountDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userService.findByEmail(email);
        if(user == null){
            throw new UsernameNotFoundException("用戶名或密碼錯誤");
        }
        List<Role> roles = roleService.findByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

繼承 UsernamePasswordAuthenticationFilter 過濾器

UsernamePasswordAuthenticationFilter 是處理用戶認證邏輯的過濾器,繼承它可制定自己的認證邏輯,可以在其中添加自己的驗證邏輯處理

通過創建AccountAuthenticationFilter 實現UsernamePasswordAuthenticationFilter過濾器類


public class AccountAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private String codeParameter = "code";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        String code = this.obtainCode(request);
        String caChecode = (String)request.getSession().getAttribute("VERCODE_KEY");
        boolean flag = CodeValidate.validateCode(code,caChecode);
        if(!flag){
            throw new UsernameNotFoundException("驗證碼錯誤");
        }
        if(username == null) {
            username = "";
        }

        if(password == null) {
            password = "";
        }
        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }


    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(this.codeParameter);
    }
}

將用戶名和密碼封裝到 UsernamePasswordAuthenticationToken 對象中

實現 AccessDeniedHandler 接口

當用戶訪問未被保護的資源的時候,爲了保證其友好性,提供統一的返回

創建MyAccessDeniedHandler實現AccessDeniedHandler類


public class MyAccessDeniedHandler implements AccessDeniedHandler {

    private String errorPage;

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        boolean isAjax = "XMLHttpRequest".equals(httpServletRequest.getHeader("X-Requested-With"));
        if (isAjax) {
            String jsonObject = "{\"message\":\"Access is denied!\",\"access-denied\":true}";
            String contentType = "application/json";
            httpServletResponse.setContentType(contentType);
            PrintWriter out = httpServletResponse.getWriter();
            out.print(jsonObject);
            out.flush();
            out.close();
            return;
        } else {
            if (!httpServletResponse.isCommitted()) {
                if (this.errorPage != null) {
                    httpServletRequest.setAttribute("SPRING_SECURITY_403_EXCEPTION", e);
                    httpServletResponse.setStatus(403);
                    RequestDispatcher dispatcher = httpServletRequest.getRequestDispatcher(this.errorPage);
                    dispatcher.forward(httpServletRequest, httpServletResponse);

                } else {
                    httpServletResponse.sendError(403, e.getMessage());
                }
            }
        }
    }
    public void setErrorPage(String errorPage) {
        if(errorPage != null && !errorPage.startsWith("/")) {
            throw new IllegalArgumentException("errorPage must begin with '/'");
        } else {
            this.errorPage = errorPage;
        }
    }
}

spring-security.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       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.1.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <security:http security="none" pattern="/css/**" />
    <security:http security="none" pattern="/js/**" />
    <security:http security="none" pattern="/images/**" />
    <security:http security="none" pattern="/favicon.ico"/>
    <security:http security="none" pattern="/login*" />
    <security:http security="none" pattern="/checkCode"/>
    <security:http security="none" pattern="/checkEmail"/>
    <security:http security="none" pattern="/checkPhone"/>
    <security:http security="none" pattern="/captchaServlet"/>
    <security:http security="none" pattern="/activecode*"/>
    <security:http security="none" pattern="/sendEmail*"/>
    <security:http security="none" pattern="/register*" />
    <security:http security="none" pattern="/doRegister" />
    <security:http security="none" pattern="/accessDenied"/>
	<security:http security="none" pattern="/reply"/>

    <security:http auto-config="false" access-decision-manager-ref="accessDecisionManager"
                    use-expressions="true" entry-point-ref="loginEntryPoint">
		
		 <security:headers>
            <security:frame-options disabled="true"></security:frame-options>
		 </security:headers>
	 
        <security:form-login login-page="/login" authentication-failure-url="/login?error=1"
                             login-processing-url="/doLogin" password-parameter="password"
                             default-target-url="/list"
                             username-parameter="username" />

        <security:access-denied-handler ref="accessDeniedHandler" />
       <!-- 禁用csrf-->
        <security:csrf disabled="true"/>
        <security:intercept-url pattern="/" access="permitAll"/>
        <security:intercept-url pattern="/index**" access="permitAll"/>
        <security:intercept-url pattern="/sendSms" access="permitAll"/>
        <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

        <!-- session失效url session策略-->
        <security:session-management invalid-session-url="/index.jsp"  session-authentication-strategy-ref="sessionStrategy">
        </security:session-management>

        <!-- spring-security提供的過濾器 以及我們自定義的過濾器 authenticationFilter-->
        <security:custom-filter ref="logoutFilter" position="LOGOUT_FILTER" />
        <security:custom-filter before="FORM_LOGIN_FILTER" ref="authenticationFilter"/>
        <security:custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
    </security:http>
    <bean id="accessDeniedHandler"
                class="yuan.boke.www.security.account.MyAccessDeniedHandler">
        <property name="errorPage" value="/accessDenied.jsp" />
    </bean>

    <bean id="loginEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <!-- 默認登錄頁的url -->
        <constructor-arg value="/login?error=login"/>
    </bean>

    <!-- 啓用表達式 爲了後面的投票器做準備 -->
    <bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler"
          id="expressionHandler"/>
    <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"
          id="expressionVoter">
        <property name="expressionHandler" ref="expressionHandler"/>
    </bean>

    <!-- 認證管理器,使用自定義的accountService,並對密碼採用md5加密 -->
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider user-service-ref="accountService">
            <security:password-encoder hash="md5">
                <security:salt-source user-property="username"></security:salt-source>
            </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean id="authenticationFilter" class="yuan.boke.www.security.account.AccountAuthenticationFilter">
        <property name="filterProcessesUrl" value="/doLogin"></property>
        <property name="authenticationManager" ref="authenticationManager"></property>
        <property name="sessionAuthenticationStrategy" ref="sessionStrategy"></property>
        <property name="authenticationSuccessHandler">
            <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
                <property name="defaultTargetUrl" value="/list"></property>
            </bean>
        </property>
        <property name="authenticationFailureHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login?error=fail"></property>
            </bean>
        </property>
    </bean>

    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <!-- 處理退出的虛擬url -->
        <property name="filterProcessesUrl" value="/loginout" />
        <!-- 退出處理成功後的默認顯示url -->
        <constructor-arg index="0" value="/login?logout" />
        <constructor-arg index="1">
            <!-- 退出成功後的handler列表 -->
            <array>
                <bean id="securityContextLogoutHandler"
                      class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
            </array>
        </constructor-arg>
    </bean>

    <!-- ConcurrentSessionFilter過濾器配置(主要設置賬戶session過期路徑) -->
    <bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
        <constructor-arg ref="sessionRegistry"></constructor-arg>
        <constructor-arg value="/login?error=expired"></constructor-arg>
    </bean>

    <bean id="sessionStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
        <constructor-arg>
            <list>

                <bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
                    <property name="maximumSessions" value="1"></property>
                    <property name="exceptionIfMaximumExceeded" value="false"></property>
                    <constructor-arg ref="sessionRegistry"/>
                </bean>
                <bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"/>
                <bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
                    <constructor-arg ref="sessionRegistry"/>
                </bean>
            </list>

        </constructor-arg>
    </bean>
    <bean id="sessionRegistry" scope="singleton" class="org.springframework.security.core.session.SessionRegistryImpl"></bean>
    <bean id="accountService" class="yuan.boke.www.security.account.AccountDetailsService"/>

    <!-- An access decision voter that reads ROLE_* configuration settings -->
    <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
    <bean id="authenticatedVoter"
          class="org.springframework.security.access.vote.AuthenticatedVoter"/>

    <bean id="accessDecisionManager"
          class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg>
            <list>
                <ref local="roleVoter"/>
                <ref local="authenticatedVoter"/>
                <ref local="expressionVoter"/>
            </list>
        </constructor-arg>
    </bean>

</beans>

web.xml 配置

在 web.xml 中配置 Spring Security 的權限過濾器鏈

 <!-- 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>

在 web.xml 中引入 spring-security.xml

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:spring-mybatis.xml,
            classpath*:applicationContext-redis.xml,
            classpath*:applicationContext-activemq.xml,
            classpath:applicationContext-solr.xml,
            classpath:spring-security.xml
        </param-value>

    </context-param>

 

發佈了19 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章