3
回答
spring security 4 注册登陆问题
【腾讯云】校园拼团福利,1核2G服务器10元/月!>>>   

一、用户表

# ---------------------------------------------------
DROP TABLE IF EXISTS `m_user`;
CREATE TABLE `m_user` (
  `id`          VARCHAR(36)  NOT NULL
  COMMENT 'ID',
  `username`    VARCHAR(32)  NOT NULL UNIQUE
  COMMENT '用户名',
  `password`    VARCHAR(64)  NOT NULL
  COMMENT '密码',
  `head`        VARCHAR(255) NULL     DEFAULT NULL
  COMMENT '用户头像',
  `realname`    VARCHAR(32)  NULL     DEFAULT NULL
  COMMENT '真实姓名',
  `nickname`    VARCHAR(32)  NULL     DEFAULT NULL
  COMMENT '昵称',
  `sex`         INT(11)      NULL     DEFAULT NULL
  COMMENT '性别1:男2:女0:保密',
  `birthday`    DATE         NULL     DEFAULT NULL
  COMMENT '生日',
  `phone`       VARCHAR(16)  NULL     DEFAULT NULL
  COMMENT '手机号码',
  `telephone`   VARCHAR(16)  NULL     DEFAULT NULL
  COMMENT '固定电话',
  `email`       VARCHAR(32)  NULL     DEFAULT NULL
  COMMENT '邮箱',
  `address`     VARCHAR(128) NULL     DEFAULT NULL
  COMMENT '地址',
  `status`      INT(11)      NOT NULL DEFAULT 1
  COMMENT '状态0:禁用1:启用',
  `certify`     INT(11)      NOT NULL DEFAULT 0
  COMMENT '认证等级0:未认证1:已认证',
  `role`        VARCHAR(64)  NOT NULL DEFAULT 'USER'
  COMMENT '用户类型USER:普通用户ADMIN:管理员',
  `experiment`  INT(11)      NOT NULL DEFAULT 0
  COMMENT '尝试登录次数',
  `salt`        VARCHAR(64)  NOT NULL
  COMMENT '盐值',
  `back_time`   TIMESTAMP    NULL     DEFAULT NULL
  COMMENT '上次登录时间',
  `create_time` TIMESTAMP    NULL     DEFAULT NULL
  COMMENT '创建时间',
  `update_time` TIMESTAMP    NULL     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  COMMENT '修改时间',
  PRIMARY KEY (`id`)
)
  COLLATE = 'utf8_general_ci'
  DEFAULT CHARSET = utf8
  ENGINE = InnoDB;
ALTER TABLE `m_user`
  COMMENT '用户基本信息表';



二、用户实体

package com.mzw.dragon.dal.common;

import com.mzw.dragon.common.util.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.io.Serializable;
import java.util.Date;

/**
 * Created by victor.min on 2016/10/20.
 */
@MappedSuperclass
public class EntityBase implements Serializable {
    private static final long serialVersionUID = 6476558187980619969L;

    @Id
    @GeneratedValue(generator = "idGenerator")
    @GenericGenerator(name = "idGenerator", strategy = "uuid2") //这个是hibernate的注解/生成32位UUID
    protected String id;

    /**
     * 状态0:禁用1:启用
     */
    @Column(name = "status")
    protected Integer status;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    @Temporal(TemporalType.TIMESTAMP)
    protected Date createTime;

    /**
     * 修改时间
     */
    @Column(name = "update_time")
    @Temporal(TemporalType.TIMESTAMP)
    protected Date updateTime;

   // getter and setter 方法省略 @Override
    public String toString() {
        return ToString.toString(this);
    }

}



package com.mzw.dragon.dal.entity;

import com.mzw.dragon.common.util.ToString;
import com.mzw.dragon.dal.common.EntityBase;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.io.Serializable;
import java.util.Date;

/**
 * Created by victor.min on 2016/9/7.
 */
@Entity
@DynamicInsert
@DynamicUpdate
@Table(name = "USER")
@NamedQuery(name = "UserEntity.findAll", query = "SELECT a FROM UserEntity a")
public class UserEntity extends EntityBase {

    private static final long serialVersionUID = 7347300725333224528L;

    /**
     * 用户名
     */
    @Column(name = "username", nullable = false, unique = true, updatable = false)
    private String username;

    /**
     * 密码'
     */
    @Column(name = "password")
    private String password;

    /**
     * 用户头像
     */
    @Column(name = "head")
    private String head;

    /**
     * 真实姓名
     */
    @Column(name = "realname")
    private String realname;

    /**
     * 昵称
     */
    @Column(name = "nickname")
    private String nickname;

    /**
     * 性别0:保密1:男2:女
     */
    @Column(name = "sex")
    private Integer sex;

    /**
     * 生日
     */
    @Column(name = "birthday")
    @Temporal(TemporalType.DATE)
    private Date birthday;

    /**
     * 手机号码
     */
    @Column(name = "phone")
    private String phone;

    /**
     * 固定电话
     */
    @Column(name = "telephone")
    private String telephone;

    /**
     * 邮箱
     */
    @Column(name = "email")
    private String email;

    /**
     * 地址
     */
    @Column(name = "address")
    private String address;

    /**
     * 认证等级0:未认证1:已认证
     */
    @Column(name = "certify")
    private Integer certify;

    /**
     * 用户类型1:普通用户2:作家3:设计师
     */
    @Column(name = "role")
    @Enumerated(EnumType.STRING)
    private RoleEnum role = RoleEnum.USER;

    /**
     * 尝试登录次数
     */
    @Column(name = "experiment")
    private Integer experiment;

    /**
     * 盐值
     */
    @Column(name = "salt", updatable = false)
    private String salt;

    /**
     * 上次登录时间
     */
    @Column(name = "back_time")
    @Temporal(TemporalType.TIMESTAMP)
    private Date backTime;

    public enum RoleEnum {
        USER, ADMIN,;
    }

   // getter and setter 方法省略

    @Override
    public String toString() {
        return ToString.toString(this);
    }

}



三、spring security configuration
package com.mzw.dragon.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Created by victor.min on 2016/10/21.
 */
@Configuration
@EnableWebSecurity
public class DragonWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
//    @Autowired
//    private RestAuthenticationFailureHandler authenticationFailureHandler;
//    @Autowired
//    private RestAuthenticationSuccessHandler successHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);
        http
//                .requestMatchers()
//                .antMatchers("/xxx/**")
//                .and()
                .authorizeRequests()
                .antMatchers("/email/**").permitAll()
                .antMatchers("/sms/**").permitAll()
                .antMatchers("/manager/member/goRegister.htm").permitAll()
                .antMatchers("/manager/member/register.htm").permitAll()
                .antMatchers("/manager/member/checkUserExist.htm").permitAll()
//                .antMatchers("").permitAll()
//                .antMatchers("").permitAll()
//                .antMatchers("").permitAll()
                .anyRequest().authenticated()
//                .anyRequest().permitAll()
                .and()
                .formLogin()
//        http://download.csdn.net/detail/muddled/8981809
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/manager/member/login.htm")
                .loginPage("/manager/member/goLogin.htm").permitAll()
                .and()
                .logout()
                .permitAll();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/front/**");
    }

    @Override
    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
//        auth
//                .inMemoryAuthentication()
//                .withUser(CommonUtil.generateUUID())
//                .password(CommonUtil.generateUUID())
//                .roles("USER");
    }

}



四、custom user details service
package com.mzw.dragon.biz.security;

import com.mzw.dragon.dal.entity.UserEntity;
import com.mzw.dragon.dal.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Created by victor.min on 2016/10/24.
 */
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        final UserEntity userEntity = userRepository.findByUsername(s);
        if (userEntity == null) {
            throw new UsernameNotFoundException(s + " cannot be found");
        }
        final Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(userEntity.getRole().name()));//此处设置用户角色为USER,只是为了简单对应起来
        return new org.springframework.security.core.userdetails.User(userEntity.getUsername(), userEntity.getPassword(), authorities);
    }
}
五、custom password encoder

package com.mzw.dragon.biz.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * Created by victor.min on 2016/10/25.
 */
@Service
public class CustomPasswordEncoder implements PasswordEncoder {

    private static final Logger logger = LoggerFactory.getLogger(CustomPasswordEncoder.class);

    @Override
    public String encode(CharSequence charSequence) {
        logger.info("char sequence={}", charSequence);
        logger.info("char sequence to string={}", charSequence.toString());
// 这里不知道怎么搞了
        return null;
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        logger.info("char sequence={}", charSequence);
        logger.info("char sequence to string={}", charSequence.toString());
        logger.info("s={}", s);
// 这里不知道怎么样才能取出存在数据库里的盐值,
//因为注册的时候会根据一定规则生成一个盐值存在数据库,然后密码是根据盐值等一系列算出来的,如果这里取不到盐值,那么就算不出来密码,没办法验证
        return false;
    }
}



六、注册和登陆controller
package com.mzw.dragon.web.controller.manager.member;

import com.mzw.dragon.biz.manager.member.MemberService;
import com.mzw.dragon.dal.entity.UserEntity;
import com.mzw.dragon.web.base.ControllerBase;
import com.mzw.dragon.web.base.EmptyRequest;
import com.mzw.dragon.web.base.ProcessImplementor;
import com.mzw.dragon.web.common.Constants;
import com.mzw.dragon.web.controller.email.EmailController;
import com.mzw.dragon.web.controller.manager.member.form.MemberLoginRequest;
import com.mzw.dragon.web.controller.manager.member.form.MemberRegisterRequest;
import com.mzw.dragon.web.util.WebUtil;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * Created by victor.min on 2016/9/12.
 */
@Controller
@RequestMapping("/manager/member")
public class MemberController extends ControllerBase {

    private static final String PATH = "/manager/member/";

    @Autowired
    private MemberService memberService;

    @RequestMapping("goRegister" + Constants.REQUEST_SUFFIX)
    public String goRegister(ModelMap modelMap) {
        return buildViewMapping("register");
    }

    @RequestMapping("register" + Constants.REQUEST_SUFFIX)
    @ResponseBody
    public String register(MemberRegisterRequest request, ModelMap modelMap, HttpServletRequest servletRequest, HttpSession session) {
        doIt(request, modelMap, new ProcessImplementor<MemberRegisterRequest>() {

            @Override
            public void beforeProcess(MemberRegisterRequest request, ModelMap response) {
                super.beforeProcess(request, response);
                String code = (String) session.getAttribute(Constants.VERIFY_CODE_KEY + servletRequest.getRemoteAddr() + request.getUsername());
                Assert.isTrue(StringUtils.equalsIgnoreCase(code, request.getVerifyCode()), "验证码不正确");
                Assert.isTrue(!memberService.checkUserExist(request.getUsername()), "用户名已经存在");
            }

            @Override
            public void process(MemberRegisterRequest request, ModelMap response) {
                UserEntity u = memberService.register(request.getUsername(), request.getPassword());
            }
        });

        return WebUtil.ajaxJsonReturn(modelMap);
    }

    @RequestMapping("goLogin" + Constants.REQUEST_SUFFIX)
    public String goLogin(ModelMap modelMap) {
        return buildViewMapping("login");
    }

    @RequestMapping("login" + Constants.REQUEST_SUFFIX)
    @ResponseBody
    public String login(MemberLoginRequest request, ModelMap modelMap, HttpSession session) {
        doIt(request, modelMap, new ProcessImplementor<MemberLoginRequest>() {
            @Override
            public void process(MemberLoginRequest request, ModelMap response) {
                UserEntity u = memberService.login(request.getUsername(), request.getPassword());
            }
        });

        return WebUtil.ajaxJsonReturn(modelMap);
    }

    @RequestMapping("goForget" + Constants.REQUEST_SUFFIX)
    public String goForget(ModelMap modelMap) {
        return buildViewMapping("forget");
    }

    @RequestMapping("forget" + Constants.REQUEST_SUFFIX)
    @ResponseBody
    public String forget(MemberLoginRequest request, ModelMap modelMap, HttpSession session) {
        doIt(request, modelMap, new ProcessImplementor<MemberLoginRequest>() {
            @Override
            public void process(MemberLoginRequest request, ModelMap response) {
                UserEntity u = memberService.login(request.getUsername(), request.getPassword());
            }
        });

        return WebUtil.ajaxJsonReturn(modelMap);
    }


    @RequestMapping("checkUserExist" + Constants.REQUEST_SUFFIX)
    @ResponseBody
    public String checkUserExist(String username, ModelMap modelMap) {
        Map<String, Object> result = new HashedMap((int) Math.ceil(4 / 0.7));
        doIt(null, modelMap, new ProcessImplementor<EmptyRequest>() {
            @Override
            public void process(EmptyRequest request, ModelMap response) {
                result.put("isExist", memberService.checkUserExist(username));
            }
        });

        return WebUtil.ajaxJsonReturn(modelMap, result);
    }

    @Override
    protected String getMappingPath() {
        return PATH;
    }
}





HELP:

这登陆出来每次都验证不过,有什么办法自己定义加密和登陆时密码的校验吗?还有盐值一定是要放到数据库中的、、、已经被spring security搞晕了……











举报
_冢彧
发帖于2年前 3回/1K+阅
共有3个答案 最后回答: 2年前

SpringSecurity的登录验证过程概括起来就是两步:

1. 产生一个Authentication实现(例如常用的UsernamePasswordAuthenticationToken)调用AuthenticationManager的authenticate()方法获取到认证后的Authentication对象(如果过程抛出AuthenticationException表示验证失败,根据异常类又分为内部异常,用户名密码错误,用户不存在,用户被禁用等)。简单来说,验证过程就是由AuthenticationManager的authenticate方法实现的。为了在系统中同时提供多种登录方式(比如微信登录,用户名密码登录等),AuthenticationManager并不直接处理认证过程,而是将认证过程委派给内置的AuthenticationProvider来处理,一个常见实现就是DaoAuthenticationProvider。

2. 做一些后续处理,例如将获取到的已通过验证的Authentication对象放置在当前的安全上下文中,调用Session同步,从RequestCache中取出登录前请求地址回调等。或者登录失败的处理。

如果你只是要修改对密码的加盐过程,实现自己的PasswordEncoder替换默认的即可。如果你要控制整个验证Authentication的过程,可以实现一个自己的AuthenticationProvider,并把他注册给AuthenticationManager。系统里面可以有多个AuthenticationProvider实现,一般是按顺序和Authentication的具体类型来判定是否由该AuthenticationProvider处理。

Spring Security提供了上述过程的一个简单的默认实现,当然,你都可以替换。

如果觉得上面的过程看天书的话,请详细阅读Spring Security官方文档。

--- 共有 4 条评论 ---
逝水fox回复 @happymzw : 另外Spring Security目前比较推荐使用的是BCryptPasswordEncoder,他不需要加盐。(可以看一下BCrypt的介绍) 2年前 回复
逝水fox回复 @happymzw : 这个感觉还是得细读一下文档对照源码看看便于理解。可以用Maven来构建项目,会给你下相应的Java包源代码,比如matches()方法可以用IDE的功能找到所有调用他的地方,顺着这个梳理基本的调用关系就容易理清楚了。 2年前 回复
_冢彧感觉spring security好多东西呀,第一次用这个,踩了不知多少坑了,还在坑中没起来…… 2年前 回复
_冢彧就是不知道在PasswordEncoder里面怎么取回存在数库里的盐值,所以 public boolean matches(CharSequence charSequence, String s) { return false; }这个方法没办法校验密码是不是正确的……现在改成自己写了一个CustomAuthenticationProvider 2年前 回复
没用过springSecurity的登录验证,不过可以自定义登录的,登录成功然后加载登录用户的权限即可,认证过程不变

引用来自“逝水fox”的评论

SpringSecurity的登录验证过程概括起来就是两步:

1. 产生一个Authentication实现(例如常用的UsernamePasswordAuthenticationToken)调用AuthenticationManager的authenticate()方法获取到认证后的Authentication对象(如果过程抛出AuthenticationException表示验证失败,根据异常类又分为内部异常,用户名密码错误,用户不存在,用户被禁用等)。简单来说,验证过程就是由AuthenticationManager的authenticate方法实现的。为了在系统中同时提供多种登录方式(比如微信登录,用户名密码登录等),AuthenticationManager并不直接处理认证过程,而是将认证过程委派给内置的AuthenticationProvider来处理,一个常见实现就是DaoAuthenticationProvider。

2. 做一些后续处理,例如将获取到的已通过验证的Authentication对象放置在当前的安全上下文中,调用Session同步,从RequestCache中取出登录前请求地址回调等。或者登录失败的处理。

如果你只是要修改对密码的加盐过程,实现自己的PasswordEncoder替换默认的即可。如果你要控制整个验证Authentication的过程,可以实现一个自己的AuthenticationProvider,并把他注册给AuthenticationManager。系统里面可以有多个AuthenticationProvider实现,一般是按顺序和Authentication的具体类型来判定是否由该AuthenticationProvider处理。

Spring Security提供了上述过程的一个简单的默认实现,当然,你都可以替换。

如果觉得上面的过程看天书的话,请详细阅读Spring Security官方文档。

//DaoAuthenticationProvider里面的设置PasswordEncoder
public void setPasswordEncoder(Object passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        if(passwordEncoder instanceof PasswordEncoder) {
            this.setPasswordEncoder((PasswordEncoder)passwordEncoder);
        } else if(passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
            final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder)passwordEncoder;
            this.setPasswordEncoder(new PasswordEncoder() {
                public String encodePassword(String rawPass, Object salt) {
                    this.checkSalt(salt);
                    return delegate.encode(rawPass);
                }

                public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
                    this.checkSalt(salt);
                    return delegate.matches(rawPass, encPass);
                }

                private void checkSalt(Object salt) {
                    Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder");
                }
            });
        } else {
            throw new IllegalArgumentException("passwordEncoder must be a PasswordEncoder instance");
        }
    }

//这里面设置encoder的时候,spring securiry自己做了这样的处理…………

//BCryptPasswordEncoder
    public String encode(CharSequence rawPassword) {
        String salt;
        if(this.strength > 0) {
            if(this.random != null) {
                salt = BCrypt.gensalt(this.strength, this.random);
            } else {
                salt = BCrypt.gensalt(this.strength);
            }
        } else {
            salt = BCrypt.gensalt();
        }

        return BCrypt.hashpw(rawPassword.toString(), salt);
    }
// 他这里是自己生成了一个盐值



顶部