开发者社区 问答 正文

spring security 4 注册登陆问题:报错 

一、用户表

# --------------------------------------------------- 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搞晕了……

展开
收起
kun坤 2020-06-08 11:53:29 757 分享 版权
1 条回答
写回答
取消 提交回答
  • SpringSecurity的登录验证过程概括起来就是两步:
    1. 产生一个Authentication实现(例如常用的UsernamePasswordAuthenticationToken)调用AuthenticationManager的authenticate()方法获取到认证后的Authentication对象(如果过程抛出AuthenticationException表示验证失败,根据异常类又分为内部异常,用户名密码错误,用户不存在,用户被禁用等)。简单来说,验证过程就是由AuthenticationManager的authenticate方法实现的。为了在系统中同时提供多种登录方式(比如微信登录,用户名密码登录等),AuthenticationManager并不直接处理认证过程,而是将认证过程委派给内置的AuthenticationProvider来处理,一个常见实现就是DaoAuthenticationProvider。

    1. 做一些后续处理,例如将获取到的已通过验证的Authentication对象放置在当前的安全上下文中,调用Session同步,从RequestCache中取出登录前请求地址回调等。或者登录失败的处理。
      如果你只是要修改对密码的加盐过程,实现自己的PasswordEncoder替换默认的即可。如果你要控制整个验证Authentication的过程,可以实现一个自己的AuthenticationProvider,并把他注册给AuthenticationManager。系统里面可以有多个AuthenticationProvider实现,一般是按顺序和Authentication的具体类型来判定是否由该AuthenticationProvider处理。
      Spring Security提供了上述过程的一个简单的默认实现,当然,你都可以替换。

    如果觉得上面的过程看天书的话,请详细阅读Spring Security官方文档。 ######回复 @happymzw : 另外Spring Security目前比较推荐使用的是BCryptPasswordEncoder,他不需要加盐。(可以看一下BCrypt的介绍)######回复 @happymzw : 这个感觉还是得细读一下文档对照源码看看便于理解。可以用Maven来构建项目,会给你下相应的Java包源代码,比如matches()方法可以用IDE的功能找到所有调用他的地方,顺着这个梳理基本的调用关系就容易理清楚了。######感觉spring security好多东西呀,第一次用这个,踩了不知多少坑了,还在坑中没起来……######就是不知道在PasswordEncoder里面怎么取回存在数库里的盐值,所以 public boolean matches(CharSequence charSequence, String s) { return false; }这个方法没办法校验密码是不是正确的……现在改成自己写了一个CustomAuthenticationProvider######没用过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);
        }
    // 他这里是自己生成了一个盐值
    2020-06-08 13:15:45
    赞同 展开评论