本文档主要记录的是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>
来源:CSDN
作者:七五三一
链接:https://blog.csdn.net/Yuanhaoxin/article/details/104010104