springboot-security安全登录

一个简单的security安全登录示例

  1. 配置环境
    1. 添加pom依赖坐标
      <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.1.6.RELEASE</version>
          </parent>
      
          <dependencies>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-security</artifactId>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.mybatis.spring.boot</groupId>
                  <artifactId>mybatis-spring-boot-starter</artifactId>
                  <version>2.2.0</version>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
              </dependency>
              <dependency>
                  <groupId>io.jsonwebtoken</groupId>
                  <artifactId>jjwt</artifactId>
                  <version>0.9.0</version>
              </dependency>
              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>fastjson</artifactId>
                  <version>1.2.76</version>
              </dependency>
          </dependencies>
    2.  application.yml文件配置
      server:
        port: 9999
      spring:
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/db_security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: root
        main:
          allow-bean-definition-overriding: true
        redis:
          port: 6379
          host: 127.0.0.1
      logging:
        level:
          root: INFO
          org.springframework.web.servlet.DispatcherServlet: DEBUG
          org.springframework.cloud.sleuth: DEBUG
    3.  数据库就一张用户表(简单登录:用户表)
      SET NAMES utf8mb4;
      SET FOREIGN_KEY_CHECKS = 0;
      
      -- ----------------------------
      -- Table structure for tb_user
      -- ----------------------------
      DROP TABLE IF EXISTS `tb_user`;
      CREATE TABLE `tb_user`  (
        `id` int(11) NOT NULL,
        `account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        `role` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
        PRIMARY KEY (`id`) USING BTREE
      ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
      
      -- ----------------------------
      -- Records of tb_user
      -- ----------------------------
      INSERT INTO `tb_user` VALUES (1, '123456', 'admin', '$2a$10$gGKeupjLdvbaoYdAlz1MfuiT/4llYvGFU2QeQ..CQIhCXyqXekn5.', 'test');
      INSERT INTO `tb_user` VALUES (2, '789456', 'zhangsan', '$2a$10$gGKeupjLdvbaoYdAlz1MfuiT/4llYvGFU2QeQ..CQIhCXyqXekn5.', NULL);
      
      SET FOREIGN_KEY_CHECKS = 1;
    4. 编写统一返回类
      @Data
      @Accessors(chain = true)
      public class R {
          //成功状态位
          private Boolean success;
          //响应消息
          private String message;
          //响应码
          private Integer code;
          //响应数据
          private Object data;
      
          private static final String DEFAULT_SUCCESS_MESSAGE = "OK";
          private static final Integer DEFAULT_SUCCESS_CODE = 20000;
          private static final String DEFAULT_FALL_MESSAGE = "ERROR";
          private static final Integer DEFAULT_FALL_CODE = 50000;
      
          private R(Boolean success, String message, Integer code, Object data) {
              this.success = success;
              this.message = message;
              this.code = code;
              this.data = data;
          }
      
          public static R success(){
              R r = new R(true,DEFAULT_SUCCESS_MESSAGE,DEFAULT_SUCCESS_CODE,null);
              return r;
          }
          public static R success(String message){
              R r = new R(true,message,DEFAULT_SUCCESS_CODE,null);
              return r;
          }
          public static R success(Object data){
              R r = new R(true,DEFAULT_SUCCESS_MESSAGE,DEFAULT_SUCCESS_CODE,data);
              return r;
          }
          public static R success(String message,Object data){
              R r = new R(true,message,DEFAULT_SUCCESS_CODE,data);
              return r;
          }
      
          public static R fail(){
              R r = new R(false,DEFAULT_FALL_MESSAGE,DEFAULT_FALL_CODE,null);
              return r;
          }
      
          public static R fail(String message){
              R r = new R(false,message,DEFAULT_FALL_CODE,null);
              return r;
          }
          public static R fail(String message,Integer code){
              R r = new R(false,message,code,null);
              return r;
          }
      }
    5.  redis配置
      @Configuration
      public class RedisConfig {
          /**
           * retemplate相关配置
           * @param factory
           * @return
           */
          @Bean
          public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
      
              RedisTemplate<String, Object> template = new RedisTemplate<>();
              // 配置连接工厂
              template.setConnectionFactory(factory);
      
              //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
              Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
      
              ObjectMapper om = new ObjectMapper();
              // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
              om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
              // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
              om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
              jacksonSeial.setObjectMapper(om);
      
              // 值采用json序列化
              template.setValueSerializer(jacksonSeial);
              //使用StringRedisSerializer来序列化和反序列化redis的key值
              template.setKeySerializer(new StringRedisSerializer());
      
              // 设置hash key 和value序列化模式
              template.setHashKeySerializer(new StringRedisSerializer());
              template.setHashValueSerializer(jacksonSeial);
              template.afterPropertiesSet();
      
              return template;
          }
      
      }
  2.  创建实体类,实现接口UserDetails,实现获取权限方法
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public class UserBean implements UserDetails {
    
        private String account;
        private String username;
        private String password;
        private String role;
    
        @JSONField(serialize = false)
        private List<SimpleGrantedAuthority> authorities;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (authorities != null) {
                return authorities;
            }
            authorities = new ArrayList<>();
            if(null!=role){
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role);
                authorities.add(simpleGrantedAuthority);
            }
            return authorities;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }

  3.  创建mapper接口,编写sql语句
    @Mapper
    public interface UserBeanMapper {
        //根据账号查询用户信息
        @Select("select * from tb_user where account=#{account}")
        UserBean findUserById(@Param("account")String account);
    }
  4.  创建service层登录接口
    public interface UserBeanService {
    
        R login(String account,String password);
    }
  5.  实现service层接口
    @Service
    public class UserServiceImpl implements UserBeanService {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
        @Override
        public R login(String account, String password) {
    //创建用户身份验证
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(account, password);
            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            if(null==authenticate){
                return R.fail("账户名或密码错误");
            }
    //以hash方式存储redis中
            redisTemplate.boundHashOps(account).put(account, JSON.toJSONString(authenticate.getPrincipal()));
    //设置redis过期时间
            redisTemplate.boundHashOps(account).expire(100000, TimeUnit.MILLISECONDS);
            return R.success(JwtUtil.createJWT(account));
        }
    }
  6.  创建controller层
    @RestController
    public class UserBeanController {
    
        @Autowired
        private UserBeanService userBeanService;
    
        //登录
        @PostMapping("/login")
        public R login(@RequestParam("account") String account, @RequestParam("password") String password){
            return userBeanService.login(account,password);
        }
        
        //权限测试
        @GetMapping("/test")
        @PreAuthorize("hasAuthority('test')")
        public R test(){
            return R.success("这是一条提示消息","test");
        }
    }

 security相关代码配置

  1. 这里使用jwt令牌,创建jwt工具类
    /**
     * JWT工具类
     */
    public class JwtUtil {
    
        //有效期为
        public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
        //设置秘钥明文
        public static final String JWT_KEY = "mingwen";
    
        public static String getUUID(){
            String token = UUID.randomUUID().toString().replaceAll("-", "");
            return token;
        }
        
        /**
         * 生成jtw
         * @param subject token中要存放的数据(json格式)
         * @return
         */
        public static String createJWT(String subject) {
            JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
            return builder.compact();
        }
    
        /**
         * 生成jtw
         * @param subject token中要存放的数据(json格式)
         * @param ttlMillis token超时时间
         * @return
         */
        public static String createJWT(String subject, Long ttlMillis) {
            JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
            return builder.compact();
        }
    
        private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            SecretKey secretKey = generalKey();
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            if(ttlMillis==null){
                ttlMillis=JwtUtil.JWT_TTL;
            }
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            return Jwts.builder()
                    .setId(uuid)              //唯一的ID
                    .setSubject(subject)   // 主题  可以是JSON数据
                    .setIssuer("sanyue")     // 签发者
                    .setIssuedAt(now)      // 签发时间
                    .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                    .setExpiration(expDate);
        }
    
        /**
         * 创建token
         * @param id
         * @param subject
         * @param ttlMillis
         * @return
         */
        public static String createJWT(String id, String subject, Long ttlMillis) {
            JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
            return builder.compact();
        }
    
    
    
        /**
         * 生成加密后的秘钥 secretKey
         * @return
         */
        public static SecretKey generalKey() {
            byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
            SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
            return key;
        }
        
        /**
         * 解析
         *
         * @param jwt
         * @return
         * @throws Exception
         */
        public static Claims parseJWT(String jwt) throws Exception {
            SecretKey secretKey = generalKey();
            return Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(jwt)
                    .getBody();
        }
    
    
    }
  2.  创建jwt过滤器
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)) {
                filterChain.doFilter(request, response);
                return;
            }
            String account = null;
    //校验token
            try {
                Claims claims = JwtUtil.parseJWT(token);
                account = claims.getSubject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("非法token");
            }
    //判断redis该用户是否超时存在
            if(Boolean.FALSE.equals(redisTemplate.boundHashOps(account).hasKey(account))){
                throw new RuntimeException("用户登录超时请重新登录");
            }
    //取出该用户相关信息 同时延长redis存储时间
            String redisUserObj = redisTemplate.boundHashOps(account).get(account).toString();
            redisTemplate.boundHashOps(account).expire(100000, TimeUnit.MILLISECONDS);
            UserBean userBean = JSON.parseObject(redisUserObj, UserBean.class);
    //将用户信息,及权限交给security
            UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userBean,null,userBean.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request, response);
        }
    }
  3.  继承security适配器,配置security
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
        
    //密码校验规则
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/login").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            //把token校验过滤器添加到过滤器链中
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers(
                    "/login/**",
                    "/logout/**"
            );
        }
    }
  4.  实现接口UserDetailsService 并实现方法,走我们自己的业务逻辑
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserBeanMapper userBeanMapper;
    
        @Override
        public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
            return userBeanMapper.findUserById(account);
        }
    }

启动项目

  1. 启动类
    @SpringBootApplication
    public class SecurityApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(SecurityApplication.class, args);
            BCryptPasswordEncoder bean = run.getBean(BCryptPasswordEncoder.class);
            System.out.println(bean.encode("123456"));
        }
    }
  2.  测试数据
  3.  admin登录
  4.  admin访问对应权限方法
  5.  zhangsan登录 
  6. zhangsan访问权限方法 

 写的不怎么样,希望对大家有用,希望大家多多指点~~~

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值