博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot2.1版本的个人应用开发框架 - 使用SpringSecurity管理我们的访问权限2
阅读量:6071 次
发布时间:2019-06-20

本文共 9197 字,大约阅读时间需要 30 分钟。

本篇作为SpringBoot2.1版本的个人开发框架 子章节,请先阅读再次阅读本篇文章

项目地址:

参考:

在上一篇文章我们对spring security有了初步认识以后,我们这篇主要实现 从数据库查询用户来进行用户是否具有登陆的认证。

数据库表的设计

参考:

  • 这篇文章讲的非常详细,只不过有点久远,12年我还在上高一。。。

我们在做用户登陆之前,我们先要设计数据库表,RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。

按照我参考的博客中的设计好了五张基础表,字段的话可以根据自己的需求变化,生成的sql文件我放到我项目中了。

表结构确定好以后,我们在项目中把系统用户的都设置好,这里我就不一一展示代码了,可以拿之前的MybatisPlus的自动生成,正好自己又可以复习一遍之前的,添加好以后可以现在测试类中确认一下,没有错误再进行下一步。

security核心类

在上一篇笔记中我们知道security的核心类之一的WebSecurityConfigurerAdapter,我们可以在这个配置类中创建自己的用户,放行哪个请求,认证什么请求,在这篇笔记我们要实现从数据库中查询用户,所以我们要对以下核心类做实现。

  • UserDetails接口
  • UserDetailsService接口

UserDetails接口

UserDetails接口是security为我们提供的可扩展的用户信息接口,保存一些非敏感类的信息,在security模块下entity包中实现此接口类,其实就是一个实体类。

package com.ywh.security.entity;import com.fasterxml.jackson.annotation.JsonIgnore;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.Collections;/** * CreateTime: 2019-01-24 16:10 * ClassName: SecurityUserDetails * Package: com.ywh.security.entity * Describe: * security的用户详情类 * * @author YWH */public class SecurityUserDetails implements UserDetails {    /**     * 用户的密码     */    private String password;    /**     * 用户的名字     */    private String username;    /**     * 用户状态,1 表示有效用户, 0表示无效用户     */    private Integer state;    /**     * 用户的权限,可以把用户的角色信息的集合先放进来,角色代表着权限     */    private Collection
authorties; public SecurityUserDetails(String password, String username, Integer state, Collection
authorties) { this.password = password; this.username = username; this.state = state; this.authorties = authorties; } @Override public Collection
getAuthorities() { return authorties; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } /** * 指示用户的帐户是否已过期。过期的帐户无法通过身份验证。 * @return true如果用户的帐户有效(即未过期), false如果不再有效(即已过期) */ @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } /** * 指示用户是锁定还是解锁。锁定的用户无法进行身份验证。 * @return true是未锁定,false是已锁定 */ @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } /** * 指示用户的凭据(密码)是否已过期。过期的凭据会阻止身份验证 * @return true如果用户的凭证有效(即未过期), false如果不再有效(即已过期) */ @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } /** * 指示用户是启用还是禁用。禁用的用户无法进行身份验证。 * @return true用户已启用,false用户已经禁用 */ @JsonIgnore @Override public boolean isEnabled() { return state == 1; }}复制代码

UserDetailsService接口

此接口主要重写一个方法就好了loadUserByUsername,这个方法主要作用就是通过用户名查询用户的详细信息,然后放到我们的UserDetails 实现的子类中,保存在security的SecurityContextHolder中,上一篇我们通过这个来获取用户信息的。

在security模块中的service/impl中实现此接口,selectByUserName方法就是一个普通的dao接口,在文件中定义好sql语句,由于很简单我就不贴了

package com.ywh.security.service.impl;/** * CreateTime: 2019-01-25 16:39 * ClassName: SecurityUserDetailsServiceImpl * Package: com.ywh.security.service.impl * Describe: * UserDetailService的实现类 * 这个@Primary表示这个类所继承的接口有多个实现类,当不知道引入哪个的时候,优先使用@Primary所注解的类 * @author YWH */@Primary@Servicepublic class SecurityUserDetailsServiceImpl implements UserDetailsService {	// 用户查询接口    private SysUserDao sysUserDao;    @Autowired    public SecurityUserDetailsServiceImpl(SysUserDao sysUserDao) {        this.sysUserDao = sysUserDao;    }	@Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        SysUserEntity sysUserEntity = sysUserDao.selectByUserName(username);        if(sysUserEntity != null){            // stream java8的新特性,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。            // 参考http://www.runoob.com/java/java8-streams.html            List
collect = sysUserEntity.getRoles().stream().map(SysRoleEntity::getSysRoleName) .map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return new SecurityUserDetails(sysUserEntity.getSysUserPassword(),sysUserEntity.getSysUserName(),sysUserEntity.getSysUserState(),collect); } throw MyExceptionUtil.mxe(String.format("'%s'.这个用户不存在", username)); }}复制代码

修改WebSecurityConfigurerAdapter实现类

在上一篇中我们对此类进行了重写,我们知道用户配置是重写configure(AuthenticationManagerBuilder auth)方法,我们是通过内存管理器来创建用户的,这回我们实现了UserDetailsService接口,我们就可以通过这个来查询用户并使用。 具体修改如下:

package com.ywh.security.config;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfigurer extends WebSecurityConfigurerAdapter {	private UserDetailsService userDetailsService;    @Autowired    public SecurityConfigurer(UserDetailsService userDetailsService) {        this.userDetailsService = userDetailsService;    }        /**     * 用户信息配置     * @param auth 用户信息管理器     * @throws Exception 异常信息     */    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {//修改以前//        auth//                .inMemoryAuthentication()//                .withUser("root")//                .password("root")//                .roles("user")//                .and()//                .passwordEncoder(CharEncoder.getINSTANCE());//修改以后        auth                .userDetailsService(this.userDetailsService)                .passwordEncoder(passwordEncoder());    }    	/**     * 密码加密     * @return 返回加密后的密码     */    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }}复制代码

重启项目后我们可以看到效果如下,ywh这个用户是数据库中的,这个用户的密码是我手动调用PasswordEncoder 加密过后放到数据库中的,密码都是123。

@Autowiredprivate PasswordEncoder passwordEncoder;@Testpublic void getOneTest(){    System.out.println("password:" + passwordEncoder.encode("123"));}复制代码

如果输入一个数据库中没有的用户,则会提示你查无此人

自定义登陆

参考:

到此步之后我们完成了从数据库中查询用户登陆的操作,但是登陆页面是security给我们默认的页面。

想使用我们自定义的页面,security继续替我们认证,在core模块下的resource文件下创建public目录后创建我们自定义的login.html登陆页面

    登陆页面

表单登录

用户名:
密码:
复制代码

要注意form表单中的action属性,这里的值要与后面在security配置类中的loginProcessingUrl("/login")相同,我写成/core/login是因为我配置了context-path,如果你没配置不用写/core前缀

访问的index.html

    
测试登陆了!!!复制代码

修改security配置类的configure(HttpSecurity httpSecurity)方法

/**     * 配置如何通过拦截器保护我们的请求,哪些能通过哪些不能通过,允许对特定的http请求基于安全考虑进行配置     * @param httpSecurity http     * @throws Exception 异常     */    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception {        httpSecurity                // 暂时禁用csrc否则无法提交                .csrf().disable()                // 设置最多一个用户登录,如果第二个用户登陆则第一用户被踢出,并跳转到登陆页面                .sessionManagement().maximumSessions(1).expiredUrl("/login.html");        httpSecurity                // 开始认证                .authorizeRequests()                // 对静态文件和登陆页面放行                .antMatchers("/static/**").permitAll()                .antMatchers("/auth/**").permitAll()                .antMatchers("/login.html").permitAll()                // 其他请求需要认证登陆                .anyRequest().authenticated();        httpSecurity                // 表单登陆                .formLogin()                // 设置跳转的登陆页面                .loginPage("/login.html")                // .failureUrl("/auth/login?error") 设置如果出错跳转到哪个页面                // security默认使用的就是login路径认证,如果想使用自定义自行修改就可以了                .loginProcessingUrl("/login")                // 如果直接访问登录页面,则登录成功后重定向到这个页面,否则跳转到之前想要访问的页面                .defaultSuccessUrl("/index.html");    }复制代码

loginPage()中也可以填写接口路径,通过接口来返回登陆页面;通过以上代码实现了我们自己定义的登陆页面,security会为我们拦截并认证的,只是创建了两个页面和增加了两个属性。

想要自定义实现登陆成功的逻辑,可以配置successHandler()方法,要实现的接口为AuthenticationSuccessHandler,重写其中的方法为onAuthenticationSuccess()。

自定义退出

想要实现自定义退出功能的逻辑,需要实现AuthenticationFailureHandler 接口,重写其中的onAuthenticationFailure方法。或者在配置中的方法中写匿名内部类。

而关于security中的退出,也有给我们默认实现,通过“/logout”就可以实现退出功能了,在index.html界面添加一个 a 标签即可。

退出复制代码

再次提示我这里写/core是因为我配置了context-path,如果你没有配置直接写/logout即可,默认security机制会进行如下操作:

  • 使HttpSession无效
  • 清理记住密码
  • 清理SecurityContextHolder
  • 重定向/login?logout

当然我们也可以自定义退出,在configure(HttpSecurity httpSecurity)方法中添加以下内容,这里我只实现了最简单的操作

httpSecurity                // 登出                .logout()                // 登出处理,使用security默认的logout,也可以自定义路径,实现即可                .logoutUrl("/logout")                // 登出成功后跳转到哪个页面                .logoutSuccessUrl("/login.html")                .logoutSuccessHandler((request, response, authentication) -> {                    //登出成功处理函数                    System.out.println("logout success");                    response.sendRedirect("/core/login.html");                })                .addLogoutHandler((request, response, authentication) ->{                    //登出处理函数                    System.out.println("logout------");                })                // 清理Session                .invalidateHttpSession(true);复制代码

关于security上面我们实现了最基本的功能,它还可以做更多的事情,比如记住我的密码,第三方登录等等。

转载于:https://juejin.im/post/5caf0af15188251ae000532e

你可能感兴趣的文章
进程小知识
查看>>
大数据之HDFS命令行基本操作
查看>>
4.17PMP试题每日一题
查看>>
Cookie禁用了Session还可以用吗?
查看>>
【Linux】开源系统监控方案:Cacti
查看>>
linux redhat6.5 构建SSH服务及SSH构建密钥对验证
查看>>
log4j - springmvc 中设置日志文件的路径
查看>>
Linux CentOS 学习第6天(2018年6月12日)
查看>>
Java程序员从阿里、百度面试回来,这些面试题你们会吗?
查看>>
去除报表参数动态下拉列表框中的Null Value
查看>>
你所遗漏的SpringBoot日志管理知识
查看>>
Java架构师学习路线图
查看>>
MySQL数据库复制概论
查看>>
网吧行业遭到DDOS功击该怎么办?
查看>>
正则辅助工具
查看>>
MongoDB:使用explain()和Index Usage(第2部分)调查查询
查看>>
单片机最小系统-基于LPC1114
查看>>
直面升职加薪,最高效学习营——TMBA线上营等你来战!
查看>>
JVM 中的栈思考
查看>>
死磕 java集合之HashSet源码分析
查看>>