• SpringSecurity基本搭建


    Spring Security项目搭建

    Spring-Securityspring提供的安全框架,基于url实现,这一点和shiro类似,在ssm项目中配置比较繁琐,一般它用在springbootspringcloud项目中

    1. 准备一个web项目

    添加测试接口

    并测试项目没有问题后添加maven节点

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    2. 配置springsecurity

    添加springsecurity的配置文件SecurityConfig.java,并继承WebSecurityConfigurerAdapter

     

    先看看WebSecurityConfigurerAdapter的方法

     

    方法有很多重点是红框框起来的三个方法,我们需要重写他们

    第一个方法:

    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

    从入参不难看出,这个方法是对用户进行配置的,也就是说你需要对什么样的用户进行安全管理,是基于内存还是数据库?都可以在这里配置

    第二个方法:

    /**
     * 配置web的
     *
     * @param web+
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    这个方法同样不难发现是基于web的配置

    第三个方法:

    /**
     * 配置安全
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    这个就是安全相关的配置

    下边简单写一个配置来看看效果

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 密码比对器
         *
         * @return
         */
        @Bean
        PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 配置用户
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()//基于内存的用户和密码
                    .withUser("lhf").password(passwordEncoder().encode("123456")).roles("admin");
    
        }
    
    
        /**
         * 配置web的
         *
         * @param web+
         * @throws Exception
         */
        @Override
        public void configure(WebSecurity web) throws Exception {
            super.configure(web);
        }
    
    
        /**
         * 配置安全
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests().anyRequest().authenticated()//所有请求都必须登录
    .and()
    .formLogin()//开启表单登录
    .permitAll()
    .and()
    .csrf().disable()//关闭防csrf攻击
    .cors()
    ;
        }
    }

    这时请求http://localhost:8080/hello 会被强行拦截到springsecurity的内置的登录页面,然后输入用户名和密码进行登录即可。

    一个简单的登录就告一段落,下边一滴一滴的开始配置他。

    3. 准备数据库

    众所周知的,权限这一块的数据库是一般分为5个表:用户、角色、用户角色、权限、角色权限。(严格来说应该是是哪个表,但是他们之间有事多对多关系所以说多了用户角色表,和角色权限表,两个中间表)

    用户表:

    CREATE TABLE `sys_user` (
    
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    
      `username` varchar(50) NOT NULL COMMENT '用户名',
    
      `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',
    
      `real_name` varchar(20) NOT NULL COMMENT '真实姓名',
    
      `email` varchar(50) DEFAULT NULL COMMENT '电子邮件',
    
      `head_img` varchar(255) DEFAULT NULL COMMENT '头像',
    
      `phone` varchar(11) DEFAULT NULL COMMENT '联系方式',
    
      `is_lock` int(1) DEFAULT '0' COMMENT '是否锁定 0未锁定 1已锁定',
    
      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',
    
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    
      PRIMARY KEY (`id`)
    
    ) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    角色表:

    CREATE TABLE `sys_role` (
    
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    
      `role_name` varchar(10) NOT NULL COMMENT '角色名',
    
      `role` varchar(20) NOT NULL COMMENT '角色',
    
      `role_ico` varchar(50) DEFAULT NULL COMMENT '角色图标',
    
      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',
    
      PRIMARY KEY (`id`)
    
    ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    用户角色关联表:

    CREATE TABLE `sys_user_role` (
    
      `user_id` int(11) NOT NULL COMMENT '用户id',
    
      `role_id` int(11) NOT NULL COMMENT '角色id',
    
      UNIQUE KEY `uid_rid_index` (`user_id`,`role_id`) USING BTREE,
    
      KEY `user_role_rid` (`role_id`),
    
      CONSTRAINT `user_role_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    
      CONSTRAINT `user_role_uid` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
    
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    权限表:

    CREATE TABLE `sys_per` (
    
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    
      `per_name` varchar(10) NOT NULL COMMENT '权限名字',
    
      `per` varchar(20) NOT NULL COMMENT '权限',
    
      `per_ico` varchar(50) DEFAULT NULL COMMENT '权限图标',
    
      `compoment` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '组件名字',
    
      `path` varchar(50) DEFAULT NULL COMMENT '请求路径',
    
      `type` int(1) DEFAULT NULL COMMENT '0 菜单 1按钮',
    
      `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父类id',
    
      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',
    
      PRIMARY KEY (`id`)
    
    ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    角色权限关联表:

    CREATE TABLE `sys_role_per` (
    
      `role_id` int(11) NOT NULL COMMENT '角色id',
    
      `per_id` int(11) NOT NULL COMMENT '权限id',
    
      UNIQUE KEY `rid_pid_index` (`role_id`,`per_id`) USING BTREE,
    
      KEY `role_per_pid` (`per_id`),
    
      CONSTRAINT `role_per_pid` FOREIGN KEY (`per_id`) REFERENCES `sys_per` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    
      CONSTRAINT `role_per_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
    
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    这样数据库就准备好了(后边就是正常的springboot+mybatis的配置省略)

    4. UserDetails

    springsecurityuserdetails是一个重要的对象,他是一个接口,提供用户的信息(包括用户名、密码、权限等).框架提供了默认的实现User,但是往往他是不能满足我们的需求,或者说不方便,于是可以自定义一个实现

    package com.lhf.springsecurity.entity;

    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.Date;
    import java.io.Serializable;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * (SysUser)实体类
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    @Data
    public class SysUser implements Serializable, UserDetails {
        private static final long serialVersionUID = -13438204047342005L;
        /**
         * 主键
         */
        private Integer uid;
        /**
         * 用户名
         */
        private String username;
        /**
         * 密码
         */
        private String password;
        /**
         * 真实姓名
         */
        private String realName;
        /**
         * 电子邮件
         */
        private String email;
        /**
         * 头像
         */
        private String headImg;
        /**
         * 联系方式
         */
        private String phone;
        /**
         * 是否锁定 0未锁定 1已锁定
         */
        private Integer isLock;
        /**
         * 0未删除 1已删除
         */
        private Integer isDel;
        /**
         * 创建时间
         */
        private Date createTime;
    
        private List<SysRole> roles;
    
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
           return roles.stream().flatMap(item -> item.getPers().stream().map(per -> new SimpleGrantedAuthority(per.getPer()))).collect(Collectors.toSet());
    
        }
    
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
    
        @Override
        public boolean isAccountNonLocked() {
            return this.isLock == 0;
        }
    
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }

    其中GrantedAuthority 是保存用户角色信息的对象,如果需要用权限控制的话可以修改这里的实现方式。

    5. UserDetailsService

    UserDetailsService 是一个接口,他是提供用户信息的接口,他的返回值就是一个UserDetails

    所以代码这样写:

    package com.lhf.springsecurity.service;
    
    import com.lhf.springsecurity.entity.SysUser;
    import org.springframework.security.core.userdetails.UserDetailsService;
    
    import java.util.List;
    
    /**
     * (SysUser)表服务接口
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    public interface SysUserService extends UserDetailsService {
    
    
    }
    
    实现类:
    
    package com.lhf.springsecurity.service.impl;
    
    import com.lhf.springsecurity.entity.SysUser;
    import com.lhf.springsecurity.dao.SysUserDao;
    import com.lhf.springsecurity.service.SysUserService;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * (SysUser)表服务实现类
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    @Service("sysUserService")
    public class SysUserServiceImpl implements SysUserService {
        @Resource
        private SysUserDao sysUserDao;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            SysUser user = sysUserDao.login(username);
            if (user == null)
                throw new UsernameNotFoundException("用户未找到");
            return user;
        }
    }
    
     
    
    通过数据库查询出SysUser对象(前边实现UserDetails),并将他返回。
    
    到这里用户信息提供者也配置好了,剩下的只需要简单配置springsecurity即可
    
    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());//配置用户提供者,并配置密码比对器
    }

    现在请求http://localhost:8080/hello 并登陆

    6. 权限控制

    代码配置

    主要是在SecurityConfig中进行配置

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").hasAuthority("ADMIN")
    
     .antMatchers("/look").hasAuthority("LOOK")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

    .antMatchers("/hello").hasAuthority("ADMIN") 拥有ADMIN角色才能访问 /hello接口

    这时登陆后分别访问http://localhost:8080/look;http://localhost:8080/hello

    /look 接口将不会返回任何信息,还会报错

    SpringSecurity通过url控制权限,自然和shiro一样,他们是有先后顺序的如果这样

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll()//所有接口给予任何权限
                .antMatchers("/hello").hasAuthority("PER_SYSTEM")
                .antMatchers("/look").hasAuthority("LOOK")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

    在最前边给了所有路径任何权限,那么下边的配置将不会生效

    .antMatchers("/**").permitAll(); 给任何请求路径所有的权限

    注解配置

    自然springsecurity也是支持注解配置的但是需要开启自动配置

    @EnableGlobalMethodSecurity(prePostEnabled = true)

    可以把它添加到SecurityConfig上,也可以添加到启动类上

    添加权限注解

    /**
     * <p></p>
     *
     * @author zy 刘会发
     * @version 1.0
     * @since 2020/5/3
     */
    @RestController
    public class TestController {
    
        @PreAuthorize("hasAnyAuthority('PER_SYSTEM')")
        @RequestMapping("/hello")
        public String hello() {
            return "hello";
        }
    
        @RequestMapping("look")
        @PreAuthorize("hasAnyAuthority('LOOK')")
        public String look() {
            return "look";
        }
    }

    这样在登录admin,分别访问两个接口

    7. 自定义登录页面

    当然,spring官方给的登录页面略微丑陋,接下来看看如何自定义登录页面

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

    这里我自己准备了一个登录页面放在了static目录下

    页面的表单:

    <div class="form">
        <div>
            <span>登录系统</span>
            <form id="form" action="/login" method="post">
                <label>
                    用户名:
                    <input name="username" type="text"/>
                </label>
                <label>
                    密   码:
                    <input name="password" type="password">
                </label>
                <input type="submit" class="submit" value="登录"/>
            </form>
        </div>
    </div>
    
     

    重新启动项目,随便请求一个接口会跳转到该页面

    8. 自定义登录返回

    自定义登录成功的返回:

    实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法

    public class LoginSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();
    
    //authentication 存放的是该用户的所有信息
            writer.write("hello,you are success:"+authentication.getName());
    
            writer.flush();
            writer.close();
        }
    }

    将他添加到springsecurity:

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

    这里直接返回一句话,用postman进行测试:

    自定义登录失败的返回

    实现AuthenticationFailureHandler接口,重写onAuthenticationFailure

    public class LoginFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();
            writer.write("hello,you are failure:" + e.getMessage());
            writer.flush();
            writer.close();
        }
    }

    自定义未授权返回

    实现AccessDeniedHandler接口,重写handle方法

    自定义未登录时返回

    实现AuthenticationEntryPoint接口,重写commence方法

     

    /**
         * 安全配置
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//自定义登录页面的url
                    .loginProcessingUrl("/login")//登录请求的url
                    .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
    //                .successForwardUrl("/index.html")//登录成功转发url
                    .failureHandler(new LoginFailureHandler())//添加自定义登录失败页面
                    .permitAll()
                    .and()
                    .exceptionHandling()
                    .accessDeniedHandler((request, response, e) -> {
    //                    自定义无权访问返回
                        request.setCharacterEncoding("utf-8");
                        response.setCharacterEncoding("utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.write("hello,you don't have permission");
                        writer.flush();
                        writer.close();
                    })
                    .authenticationEntryPoint((request, response, e) -> {
    //                    自定义未登录时返回
                        request.setCharacterEncoding("utf-8");
                        response.setCharacterEncoding("utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.write("hello,you need login");
                        writer.flush();
                        writer.close();
                    })
                    .and()
                    .csrf().disable()
                    .cors().disable();
        }

    熟悉jdk1.8的都知道,一个接口有且只有一个方法的时候,可以使用lamda表达式,上诉代码中自定义未授权返回和自定义未登录时返回采用lamda表达式,当然登陆成功和登录失败也同样可以这样(不建议,项目比较大,而自定义返回逻辑复杂时,这个配置类将会十分的臃肿,这里只是为了方便)

    9. 记住我

    springsecurity原生支持记住我的功能,有基于内存的、还有基于数据库的

     

     /**
         * 记住我功能,通过数据库
         *
         * @return
         */
        @Bean
        PersistentTokenRepository persistentTokenRepository() {
            JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
            impl.setDataSource(dataSource);
            impl.afterPropertiesSet();
    //        impl.setCreateTableOnStartup(true); //第一次启动开启,将会创建表,之后直接关闭即可
            return impl;
        }

    这里配置一个基于数据库的实现

    impl.setCreateTableOnStartup(true) 可以生成默认表结构,在第一次启动时将参数设置为true,以后将不再需要,注释即可

     

    项目地址:

    https://github.com/Liuhuifa/spring-security

    Sql文件在项目resources目录下

    Sql文件中有一些没用的表,自行忽略

    Spring Security项目搭建

    Spring-Securityspring提供的安全框架,基于url实现,这一点和shiro类似,在ssm项目中配置比较繁琐,一般它用在springbootspringcloud项目中

    1. 准备一个web项目

    添加测试接口

     

    并测试项目没有问题后添加maven节点

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    2. 配置springsecurity

    添加springsecurity的配置文件SecurityConfig.java,并继承WebSecurityConfigurerAdapter

     

     

    先看看WebSecurityConfigurerAdapter的方法

     

     

    方法有很多重点是红框框起来的三个方法,我们需要重写他们

    第一个方法:

    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

    从入参不难看出,这个方法是对用户进行配置的,也就是说你需要对什么样的用户进行安全管理,是基于内存还是数据库?都可以在这里配置

    第二个方法:

    /**
     * 配置web
     *
     * @param web+
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

     

    这个方法同样不难发现是基于web的配置

     

    第三个方法:

    /**
     * 配置安全
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    这个就是安全相关的配置

     

    下边简单写一个配置来看看效果

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

        /**
         * 密码比对器
         *
         * @return
         */
        @Bean
        PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }

        /**
         * 配置用户
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()//基于内存的用户和密码
                    .withUser("lhf").password(passwordEncoder().encode("123456")).roles("admin");

        }


        /**
         * 配置web
         *
         * @param web+
         * @throws Exception
         */
        @Override
        public void configure(WebSecurity web) throws Exception {
            super.configure(web);
        }


        /**
         * 配置安全
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests().anyRequest().authenticated()//所有请求都必须登录
    .and()
    .formLogin()//开启表单登录
    .permitAll()
    .and()
    .csrf().disable()//关闭防csrf攻击
    .cors()
    ;
        }
    }

     

    这时请求http://localhost:8080/hello 会被强行拦截到springsecurity的内置的登录页面,然后输入用户名和密码进行登录即可。

    一个简单的登录就告一段落,下边一滴一滴的开始配置他。

    3. 准备数据库

    众所周知的,权限这一块的数据库是一般分为5个表:用户、角色、用户角色、权限、角色权限。(严格来说应该是是哪个表,但是他们之间有事多对多关系所以说多了用户角色表,和角色权限表,两个中间表)

    用户表:

     

    CREATE TABLE `sys_user` (

      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

      `username` varchar(50) NOT NULL COMMENT '用户名',

      `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码',

      `real_name` varchar(20) NOT NULL COMMENT '真实姓名',

      `email` varchar(50) DEFAULT NULL COMMENT '电子邮件',

      `head_img` varchar(255) DEFAULT NULL COMMENT '头像',

      `phone` varchar(11) DEFAULT NULL COMMENT '联系方式',

      `is_lock` int(1) DEFAULT '0' COMMENT '是否锁定 0未锁定 1已锁定',

      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

      `create_time` datetime DEFAULT NULL COMMENT '创建时间',

      PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

     

    角色表:

     

    CREATE TABLE `sys_role` (

      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

      `role_name` varchar(10) NOT NULL COMMENT '角色名',

      `role` varchar(20) NOT NULL COMMENT '角色',

      `role_ico` varchar(50) DEFAULT NULL COMMENT '角色图标',

      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

      PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    用户角色关联表:

     

    CREATE TABLE `sys_user_role` (

      `user_id` int(11) NOT NULL COMMENT '用户id',

      `role_id` int(11) NOT NULL COMMENT '角色id',

      UNIQUE KEY `uid_rid_index` (`user_id`,`role_id`) USING BTREE,

      KEY `user_role_rid` (`role_id`),

      CONSTRAINT `user_role_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

      CONSTRAINT `user_role_uid` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    权限表:

     

     

     

    CREATE TABLE `sys_per` (

      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',

      `per_name` varchar(10) NOT NULL COMMENT '权限名字',

      `per` varchar(20) NOT NULL COMMENT '权限',

      `per_ico` varchar(50) DEFAULT NULL COMMENT '权限图标',

      `compoment` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '组件名字',

      `path` varchar(50) DEFAULT NULL COMMENT '请求路径',

      `type` int(1) DEFAULT NULL COMMENT '0 菜单 1按钮',

      `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父类id',

      `is_del` int(1) DEFAULT '0' COMMENT '0未删除 1已删除',

      PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    角色权限关联表:

     

    CREATE TABLE `sys_role_per` (

      `role_id` int(11) NOT NULL COMMENT '角色id',

      `per_id` int(11) NOT NULL COMMENT '权限id',

      UNIQUE KEY `rid_pid_index` (`role_id`,`per_id`) USING BTREE,

      KEY `role_per_pid` (`per_id`),

      CONSTRAINT `role_per_pid` FOREIGN KEY (`per_id`) REFERENCES `sys_per` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,

      CONSTRAINT `role_per_rid` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

     

    这样数据库就准备好了(后边就是正常的springboot+mybatis的配置省略)

     

    4. UserDetails

    springsecurityuserdetails是一个重要的对象,他是一个接口,提供用户的信息(包括用户名、密码、权限等).框架提供了默认的实现User,但是往往他是不能满足我们的需求,或者说不方便,于是可以自定义一个实现

    package com.lhf.springsecurity.entity;

    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;

    import java.util.Collection;
    import java.util.Date;
    import java.io.Serializable;
    import java.util.List;
    import java.util.stream.Collectors;

    /**
     * (SysUser)实体类
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    @Data
    public class SysUser implements Serializable, UserDetails {
        private static final long serialVersionUID = -13438204047342005L;
        /**
         * 主键
         */
        private Integer uid;
        /**
         * 用户名
         */
        private String username;
        /**
         * 密码
         */
        private String password;
        /**
         * 真实姓名
         */
        private String realName;
        /**
         * 电子邮件
         */
        private String email;
        /**
         * 头像
         */
        private String headImg;
        /**
         * 联系方式
         */
        private String phone;
        /**
         * 是否锁定 0未锁定 1已锁定
         */
        private Integer isLock;
        /**
         * 0未删除 1已删除
         */
        private Integer isDel;
        /**
         * 创建时间
         */
        private Date createTime;

        private List<SysRole> roles;


        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
           return roles.stream().flatMap(item -> item.getPers().stream().map(per -> new SimpleGrantedAuthority(per.getPer()))).collect(Collectors.toSet());

        }


        @Override
        public boolean isAccountNonExpired() {
            return true;
        }


        @Override
        public boolean isAccountNonLocked() {
            return this.isLock == 0;
        }


        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }


        @Override
        public boolean isEnabled() {
            return true;
        }
    }

     

     

    其中GrantedAuthority 是保存用户角色信息的对象,如果需要用权限控制的话可以修改这里的实现方式。

    5. UserDetailsService

    UserDetailsService 是一个接口,他是提供用户信息的接口,他的返回值就是一个UserDetails

    所以代码这样写:

    package com.lhf.springsecurity.service;

    import com.lhf.springsecurity.entity.SysUser;
    import org.springframework.security.core.userdetails.UserDetailsService;

    import java.util.List;

    /**
     * (SysUser)表服务接口
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    public interface SysUserService extends UserDetailsService {


    }

    实现类:

    package com.lhf.springsecurity.service.impl;

    import com.lhf.springsecurity.entity.SysUser;
    import com.lhf.springsecurity.dao.SysUserDao;
    import com.lhf.springsecurity.service.SysUserService;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;
    import java.util.List;

    /**
     * (SysUser)表服务实现类
     *
     * @author 刘会发
     * @since 2020-05-03 09:28:53
     */
    @Service("sysUserService")
    public class SysUserServiceImpl implements SysUserService {
        @Resource
        private SysUserDao sysUserDao;

        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            SysUser user = sysUserDao.login(username);
            if (user == null)
                throw new UsernameNotFoundException("用户未找到");
            return user;
        }
    }

     

    通过数据库查询出SysUser对象(前边实现UserDetails),并将他返回。

    到这里用户信息提供者也配置好了,剩下的只需要简单配置springsecurity即可

    /**
     * 配置用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());//配置用户提供者,并配置密码比对器
    }

     

    现在请求http://localhost:8080/hello 并登陆

    6. 权限控制

    代码配置

    主要是在SecurityConfig中进行配置

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").hasAuthority("ADMIN")

     .antMatchers("/look").hasAuthority("LOOK")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

     

    .antMatchers("/hello").hasAuthority("ADMIN") 拥有ADMIN角色才能访问 /hello接口

     

    这时登陆后分别访问http://localhost:8080/look;http://localhost:8080/hello

    /look 接口将不会返回任何信息,还会报错

     

    SpringSecurity通过url控制权限,自然和shiro一样,他们是有先后顺序的如果这样

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll()//所有接口给予任何权限
                .antMatchers("/hello").hasAuthority("PER_SYSTEM")
                .antMatchers("/look").hasAuthority("LOOK")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

    在最前边给了所有路径任何权限,那么下边的配置将不会生效

    .antMatchers("/**").permitAll(); 给任何请求路径所有的权限

     

    注解配置

    自然springsecurity也是支持注解配置的但是需要开启自动配置

    @EnableGlobalMethodSecurity(prePostEnabled = true)

    可以把它添加到SecurityConfig上,也可以添加到启动类上

     

    添加权限注解

    /**
     * <p></p>
     *
     * @author zy 刘会发
     * @version 1.0
     * @since 2020/5/3
     */
    @RestController
    public class TestController {

        @PreAuthorize("hasAnyAuthority('PER_SYSTEM')")
        @RequestMapping("/hello")
        public String hello() {
            return "hello";
        }

        @RequestMapping("look")
        @PreAuthorize("hasAnyAuthority('LOOK')")
        public String look() {
            return "look";
        }
    }

     

    这样在登录admin,分别访问两个接口

    7. 自定义登录页面

    当然,spring官方给的登录页面略微丑陋,接下来看看如何自定义登录页面

     

     

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

     

    这里我自己准备了一个登录页面放在了static目录下

     

    页面的表单:

    <div class="form">
        <div>
            <span>登录系统</span>
            <form id="form" action="/login" method="post">
                <label>
                    用户名:
                    <input name="username" type="text"/>
                </label>
                <label>
                       :
                    <input name="password" type="password">
                </label>
                <input type="submit" class="submit" value="登录"/>
            </form>
        </div>
    </div>

     

     

    重新启动项目,随便请求一个接口会跳转到该页面

     

    8. 自定义登录返回

    自定义登录成功的返回:

    实现AuthenticationSuccessHandler接口,并重写onAuthenticationSuccess方法

    public class LoginSuccessHandler implements AuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();

    //authentication 存放的是该用户的所有信息
            writer.write("hello,you are success:"+authentication.getName());

            writer.flush();
            writer.close();
        }
    }

     

    将他添加到springsecurity:

    /**
     * 安全配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")//自定义登录页面的url
                .loginProcessingUrl("/login")//登录请求的url
                .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
                .permitAll()
                .and()
                .csrf().disable()
                .cors().disable();
    }

     

    这里直接返回一句话,用postman进行测试:

     

    自定义登录失败的返回

    实现AuthenticationFailureHandler接口,重写onAuthenticationFailure

    public class LoginFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            PrintWriter writer = response.getWriter();
            writer.write("hello,you are failure:" + e.getMessage());
            writer.flush();
            writer.close();
        }
    }

    自定义未授权返回

    实现AccessDeniedHandler接口,重写handle方法

    自定义未登录时返回

    实现AuthenticationEntryPoint接口,重写commence方法

     

     /**
         * 安全配置
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login.html")//自定义登录页面的url
                    .loginProcessingUrl("/login")//登录请求的url
                    .successHandler(new LoginSuccessHandler())//添加自定义登陆成功返回
    //                .successForwardUrl("/index.html")//登录成功转发url
                    .failureHandler(new LoginFailureHandler())//添加自定义登录失败页面
                    .permitAll()
                    .and()
                    .exceptionHandling()
                    .accessDeniedHandler((request, response, e) -> {
    //                    自定义无权访问返回
                        request.setCharacterEncoding("utf-8");
                        response.setCharacterEncoding("utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.write("hello,you don't have permission");
                        writer.flush();
                        writer.close();
                    })
                    .authenticationEntryPoint((request, response, e) -> {
    //                    自定义未登录时返回
                        request.setCharacterEncoding("utf-8");
                        response.setCharacterEncoding("utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.write("hello,you need login");
                        writer.flush();
                        writer.close();
                    })
                    .and()
                    .csrf().disable()
                    .cors().disable();
        }

     

     

    熟悉jdk1.8的都知道,一个接口有且只有一个方法的时候,可以使用lamda表达式,上诉代码中自定义未授权返回和自定义未登录时返回采用lamda表达式,当然登陆成功和登录失败也同样可以这样(不建议,项目比较大,而自定义返回逻辑复杂时,这个配置类将会十分的臃肿,这里只是为了方便)

     

    9. 记住我

    springsecurity原生支持记住我的功能,有基于内存的、还有基于数据库的

      /**
         * 记住我功能,通过数据库
         *
         * @return
         */
        @Bean
        PersistentTokenRepository persistentTokenRepository() {
            JdbcTokenRepositoryImpl impl = new JdbcTokenRepositoryImpl();
            impl.setDataSource(dataSource);
            impl.afterPropertiesSet();
    //        impl.setCreateTableOnStartup(true); //第一次启动开启,将会创建表,之后直接关闭即可
            return impl;
        }

    这里配置一个基于数据库的实现

    impl.setCreateTableOnStartup(true) 可以生成默认表结构,在第一次启动时将参数设置为true,以后将不再需要,注释即可

    项目地址:

    https://github.com/Liuhuifa/spring-security

    Sql文件在项目resources目录下

    Sql文件中有一些没用的表,自行忽略

     

  • 相关阅读:
    RabbitMQ架构面试题答不出来怎么办!大佬手绘架构图带你分分钟搞懂!
    【秋招必备】大数据面试题100道(2021最新版)
    【秋招必备】设计模式面试题(2021最新版)
    【秋招必备】TCP,UDP,Socket,Http网络编程面试题(2021最新版)
    3分钟带你玩转MySQL体系结构和查询原理!
    易车面试官:说说MySQL内存结构、索引、集群、底层原理!
    【秋招必备】Mybatis面试题(2021最新版)
    iOS-项目开发1
    ReactNatvie遇到的错误
    细节
  • 原文地址:https://www.cnblogs.com/Tiandaochouqin1/p/12822940.html
Copyright © 2020-2023  润新知