返回介绍

5.3 登录认证设计

发布于 2025-04-26 13:26:33 字数 9555 浏览 0 评论 0 收藏

完成上面的安全策略配置之后,打开受保护的页面或链接时,就会引导用户到登录页面上输入用户名和密码验证用户身份。如果在安全配置中没有指定登录页面 URL,Spring Security 就调用其默认的登录页面。只是,Spring Security 的登录页面设计很简单,不适合于一般的 Web 应用的登录设计。除了登录页面,Spring Security 对于用户身份验证同样也已经实现了,只需要加以引用即可。

5.3.1 用户实体建模

可以使用第 2 章的实例工程 MySQL 模块的实体建模来建立用户体系,回顾一下,在第 2 章中建模的实体中包含用户、部门和角色三个对象,它们的关系是,一个用户只能属于一个部门,一个用户可以拥有多个角色,这非常适合本章的实例。除了部门和角色,用户实体的属性必须做些调整,以适合本章实例的要求,如代码清单 5-11 所示,即增加了邮箱、性别和密码等几个属性,其他基本相同。

代码清单 5-11 用户实体建模

@Entity
@Table(name = "user")
public class User implements java.io.Serializable{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    private Integer sex;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createdate;
    private String password;
    @ManyToOne
    @JoinColumn(name = "did")
    @JsonBackReference
    private Department department;
    @ManyToMany(cascade = {}, fetch = FetchType.EAGER)
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "roles_id")})
    private List<Role> roles;
    ......
}

另外,在用户实体的持久化方面,也增加了几个方法以便能适用本章实例的要求,如代码清单 5-12 所示。其中 User findByName(String name)就是登录时使用用户名来查询用户的信息。

代码清单 5-12 用户实体持久化接口

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("select t from User t where t.name =?1 and t.email =?2")
    User findByNameAndEmail(String name, String email);
    @Query("select t from User t where t.name like :name")
    Page<User> findByName(@Param("name") String name, Pageable pageRequest);
    User findByName(String name);
}

5.3.2 用户身份验证

在安全配置类的定义中,使用了如代码清单 5-13 所示的配置,用来调用我们自定义的用户认证 CustomUserDetailsService,并且指定了使用密码的加密算法为 BCryptPasswordEncoder,这是 Spring Security 官方推荐的加密算法,比 MD5 算法的安全性更高。

代码清单 5-13 安全配置类引用

@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.userDetailsService(customUserDetailsService).passwordEncoder
(passwordEncoder());
        // remember me
        auth.eraseCredentials(false);
}
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

如代码清单 5-14 所示,CustomUserDetailsService 实现了 Spring Security 的 User-DetailsService,重载了 loadUserByUsername(String userName),并返回自定义的 Security-User,通过这个 SecurityUser 来完成用户的身份认证。其中,loadUserByUsername 调用了用户资源库接口的 findByName 方法,取得登录用户的详细信息。

代码清单 5-14 CustomUserDetailsService 定义

@Component
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNot
FoundException {
        User user = userRepository.findByName(userName);
        if (user == null) {
            throw new UsernameNotFoundException("UserName " + userName + "
not found");
        }
        return new SecurityUser(user);
    }
}

SecurityUser 继承于实体对象 User,并实现了 Spring Security 的 UserDetails,同时重载了 getAuthorities(),用来取得为用户分配的角色列表,用于后面的权限验证,它的实现如代码清单 5-15 所示。

代码清单 5-15 SecurityUser 定义

public class SecurityUser extends User implements UserDetails
{
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<Granted
Authority>();
        List<Role> roles = this.getRoles();
        if(roles != null)
        {
            for (Role role : roles) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority
(role.getName());
                authorities.add(authority);
            }
        }
        return authorities;
    }
    ......
}

5.3.3 登录界面设计

首先创建一个登录控制器,编写如代码清单 5-16 所示的代码,这个控制器很简单,它仅仅是返回对一个页面的调用,页面的设计文件是 login.html。

代码清单 5-16 登录控制器

@Controller
public class LoginController {
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
}

登录界面的设计在页面文件 login.html 中完成,代码清单 5-17 是表单设计的部分代码和一些错误提示设计。表单中设置了用户、密码和验证码等输入框,最终使用 POST 方式提交,提交的链接地址是/loging,这将请求 Spring Security 的内部方法。

代码清单 5-17 登录界面表单设计

                <div th:if="${param.error}">
                <input th:value="无效的用户或密码!




" id="errorMsg" type="hidden"/>
            </div>
            <div th:if="${param.logout}">
                <input th:value="你已经退出!




" id="errorMsg" type="hidden"/>
            </div>
            <div th:if="${#httpServletRequest.remoteUser != null}">
                <input th:value="${#httpServletRequest.remoteUser}" id="errorMsg"
type="hidden"/>
            </div>
<form th:action="@{/login}" id="loginForm" method="post">
                <div class="loginTit png"></div>
                <ul class="infList">
                    <li class="grayBox">
                        <label for="username" class="username-icon"></label>
                        <input id="username" class="username" name="username"
type="text" placeholder="您的用户名




"/>
                        <div class="close png hide"></div>
                    </li>
                    <li class="grayBox">
                        <label class="pwd-icon" id="pwd"></label>
                        <input id="password" name="password" class="pwd" type=
"password" placeholder="登录密码




"/>
                        <div class="close png hide"></div>
                    </li>
                    <li class="">
                        <label class="validateLabel" ></label>
                        <input id="checkCode" name="checkCode" class="checkCode"
type="text" placeholder="验证码




" />
                        <img onclick="reloadImg();"
th:src="@{/images/imagecode}" id="validateImg" alt="验证码




" class="codePic" title=
"验证码。单击此处更新验证码。




"/>
                        <a class="getOther" href="javascript:void(0);" onclick=
"reloadImg();" title="单击此处可以更新验证码。




">更新




</a>
                    </li>
                </ul>
                <ul class="infList reloadBtn">
                    <li>
                        <a href="javascript:void(0);" onclick="tologin();">本页面已经失效。请单击此处重新登录。




</a>
                    </li>
                </ul>
                <div class="loginBtnBox">
                    <div class="check-box"><input type="hidden" value="0" id=
"remember-me" name="remember-me" onclick="if(this.checked){this.value = 1}
else{this.value=0}" /><span class="toggleCheck no-check" id="repwd"></span>记住




我




</div>
                    <input type="button" id="loginBtn" onclick="verSubmit()"
value="登录




" class="loginBtn png" />
                </div>
            </form>

完成的登录界面设计效果如图 5-1 所示。

图 5-1 登录界面设计效果图

5.3.4 验证码验证

注意到上面登录界面设计中有一个验证码功能,这个功能 Spring Security 是没有的,必须由我们来实现。代码清单 5-18 是使用验证码的实现代码,其中 imagecode 方法是一个生成图形验证码的请求,checkcode 方法实现了对这个图形验证码的验证。从验证码的生成到验证的过程中,验证码是通过 Session 来保存的,并且设定一个验证码的最长有效时间为 5 分钟。验证码的生成规则是从 0~9 的数字中,随机产生一个 4 位数,并增加一些干扰元素,最终组合成为一个图形输出。

代码清单 5-18 验证码验证

@RequestMapping(value = "/images/imagecode")
    public String imagecode(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        OutputStream os = response.getOutputStream();
        Map<String,Object> map = ImageCode.getImageCode(60, 20, os);
        String simpleCaptcha = "simpleCaptcha";
        request.getSession().setAttribute(simpleCaptcha, map.get("strEnsure").
toString().toLowerCase());
        request.getSession().setAttribute("codeTime",new Date().getTime());
        try {
            ImageIO.write((BufferedImage) map.get("image"), "JPEG", os);
        } catch (IOException e) {
            return "";
        }
        return null;
    }
    @RequestMapping(value = "/checkcode")
    @ResponseBody
    public String checkcode(HttpServletRequest request, HttpSession session)
            throws Exception {
        String checkCode = request.getParameter("checkCode");
        Object cko = session.getAttribute("simpleCaptcha") ; // 验证码对象





        if(cko == null){
            request.setAttribute("errorMsg", "验证码已失效,请重新输入!




");
            return "验证码已失效,请重新输入!




";
        }
        String captcha = cko.toString();
        Date now = new Date();
        Long codeTime = Long.valueOf(session.getAttribute("codeTime")+"");
        if(StringUtils.isEmpty(checkCode) || captcha == null ||  !(checkCode.
equalsIgnoreCase(captcha))){
            request.setAttribute("errorMsg", "验证码错误!




");
            return "验证码错误!




";
        }else if ((now.getTime()-codeTime)/1000/60>5){// 验证码有效时长为




5 分钟





            request.setAttribute("errorMsg", "验证码已失效,请重新输入!




");
            return "验证码已失效,请重新输入!




";
        }else {
            session.removeAttribute("simpleCaptcha");
            return "1";
        }
    }

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。