1. shiro-用户认证

shiro-用户认证

#application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 12345
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5 #初始连接数
      max-active: 10 #最大活动连接
      max-wait: 60000 #从池中取连接(没有闲置连接)的最大等待时间,-1表示无限等待
      min-idle: 5 #最小闲置数,小于min-idle连接池会主动创建新的连接
      time-between-eviction-runs-millis: 60000 #清理线程启动的间隔时间,当线程池中没有可用的连接启动清理线程
      min-evictable-idle-time-millis: 300000 #清理线程保持闲置最小时间
      validation-query: SELECT 1  #用于校验连接
      test-on-borrow: false #请求连接时是否校验,比较消耗性能,一般设置false
      test-on-return: false #归还连接时是否校验,比较消耗性能,一般设置false
      test-while-idle: true #清理线程通过validation-query来校验连接是否正常,如果不正常将从连接池中移除
      pool-prepared-statements: true #存储相同逻辑的sql到连接池的缓存中
      #      filters: stat,wall #监控统计web的statement(sql),以及防sql注入的wall
      # 关闭如上配置,可以采用自定义的filter
      filter:
        stat:
          enabled: true #状态监控-->stat
          db-type: mysql
          log-slow-sql: true  #记录超过指定时间的sql到日志中
          slow-sql-millis: 2000
        wall:
          enabled: true #防火墙-->wall
          db-type: mysql
          config:
            delete-allow: false #禁止删除
            drop-table-allow: false #禁止删除表
      web-stat-filter:
        enabled: true #开启监控uri,默认false
        url-pattern: /* #添加过滤规则
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid" #忽略过滤
      stat-view-servlet:
        enabled: true #开启视图管理界面,默认false
        url-pattern: /druid/* #视图管理界面uri
        login-username: druid #账号
        login-password: 12345 #密码
#        allow: 127.0.0.1 白名单
#        deny:  192.168.1.130黑名单
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启log日志
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
    lazy-load-trigger-methods: ""
  global-config:
    db-config:
      logic-delete-field: deleted #逻辑删除
      logic-delete-value: 1 #已删除的值, 默认1
      logic-not-delete-value: 0 #未删除的, 默认0


#mybaits-plus generator

    @Test
    public void generator() {
        //主入口函数的路径
        System.out.println(System.getProperty("user.dir"));

        //代码生成器
        AutoGenerator autoGenerator = new AutoGenerator();
        //全局配置 调用generator.config下的
        GlobalConfig gc = new GlobalConfig();
        //获取当前项目的路径
        String path = System.getProperty("user.dir");
        //设置是否开启AR
        gc.setAuthor("chz")
                //文件输出路径
                .setOutputDir(path + "/src/main/java")
                //是否覆盖文件
                .setFileOverride(true)
                //设置主键自增策略
                .setIdType(IdType.AUTO)
                //是否开启resultMap,默认false
                .setBaseResultMap(true)
                //是否开启sql片段,默认false
                .setBaseColumnList(true);

        //数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL)
                .setDriverName("com.mysql.cj.jdbc.Driver")
                .setUrl("jdbc:mysql://localhost:3306/shiro?userSSL=false&serverTimezone=Asia/Shanghai")
                .setUsername("root")
                .setPassword("12345");

        //策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        //是否开启大写命名,默认不开启
        strategyConfig.setCapitalMode(false)
                //数据库表映射到实体类命名策略
                .setNaming(NamingStrategy.underline_to_camel)
                //设置想要生成的表
                .setInclude("t_perm","t_user","t_role")
                //生成的dao,service,entity不再带tbl_前缀
                .setTablePrefix("t_");


        //包配置
        PackageConfig packageConfig = new PackageConfig();
        //setParent设置统一的包路径
        packageConfig.setParent("com.chz")
                //设置包的路径
                .setMapper("mapper")
                .setService("service")
                .setController("controller")
                .setEntity("entity")
                .setXml("mapper");

        //整合配置
        autoGenerator.setPackageInfo(packageConfig)
                .setDataSource(dataSourceConfig)
                .setGlobalConfig(gc)
                .setStrategy(strategyConfig);
        //执行
        autoGenerator.execute();
    }

#pojo

//用户
@Data
@TableName("t_user")
public class User implements Serializable {
    private static final long serialVersionUID=1L;
    @TableId(value = "u_id", type = IdType.AUTO)
    private Integer uId;
    private String name;
    private String password;
    @TableField(exist = false)
    private List<Role> roles;
}
//角色
@Data
@TableName("t_role")
public class Role implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "r_id", type = IdType.AUTO)
    private Integer rId;
    private String role;
    @TableField(exist = false)
    private List<Perm> perms;
}
//权限
@Data
@TableName("t_perm")
public class Perm implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "p_id", type = IdType.AUTO)
    private Integer pId;
    private String perm;
}

#mapper

在主启动类上使用了@MapperScan, 无需再使用@Mapper

//user
public interface UserMapper extends BaseMapper<User> {
    @Select("SELECT * FROM t_user WHERE name =#{username}")
    @Results({
            @Result(property = "uId",column = "u_id",id = true),
            @Result(property = "roles",column = "u_id",
                    many = @Many(select = "com.chz.mapper.RoleMapper.queryRole",fetchType = FetchType.LAZY))
    })
    User queryUser(@Param("username") String username);
}

//role
public interface RoleMapper extends BaseMapper<Role> {
    @Select("SELECT * FROM t_role WHERE r_id IN (SELECT r_id FROM t_u_r WHERE u_id = #{u_id})")
    @Results({
            @Result(property = "rId", column = "r_id"),
            @Result(property = "perms",column = "r_id",
            many =@Many(select = "com.chz.mapper.PermMapper.queryPerms",fetchType = FetchType.LAZY))
    })
    Role queryRole(@Param("u_id") Integer uid);
}
//perm
public interface PermMapper extends BaseMapper<Perm> {
    @Select("SELECT * FROM t_perm WHERE p_id IN (SELECT p_id FROM t_r_p WHERE r_id = #{r_id})")
    List<Perm> queryPerms(@Param("r_id") Integer rId);
}

#Mvc配置类

/**
 * Mvc配置类
 */
@Configuration
public class MvcConf implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index").setViewName("index");
    }
}

#shiro配置类

/**
 * shiro配置类
 * 三大核心 ShiroFilterFactoryBean: filter 过滤url,交给SecurityManager代理
 * SecurityManager: 代理filter和Realm
 * Realm: 连接数据库的桥梁,权限与用户认证
 */
@Configuration
public class ShiroConf {
    /**
     *
     * @param manager 安全代理
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager manager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //关联SecurityManager
        shiroFilterFactoryBean.setSecurityManager(manager);
        //通过set的方式设置url会放行请求
        //登入的url
        shiroFilterFactoryBean.setLoginUrl("/login");
        //登入成功的url(由于请求是通过ajax发送的,所以不会生效,这里只起请求放行作用)
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //设置未授权跳转的url
        //shiroFilterFactoryBean.setUnauthorizedUrl("/");
        //设置过滤,必须使用LinkedHashMap,否则会出现资源加在一次就被拦截
        LinkedHashMap<String, String> filterChain = new LinkedHashMap<>();
    /*
    注意 anon 必须放在authc之上
    支持ant
    常用过滤器:
    1. anon (anonymous): 无需认证(登入)可以访问资源
    2. authc (authority): 必须认证才能访问
    3. user : 如果使用rememberMe的功能可以访问, 同时认证通过的也可以访问
    4. perms (permissions): 该资源必须拥有对应的资源权限才能访问
    5. roles: 该资源源必须拥有对应的角色才能访问
     */
        //filter会拦截所有请求,包括静态资源的请求
//        filterChain.put("/css/**", "anon");
//        filterChain.put("/js/**", "anon");
//        filterChain.put("/img/**", "anon");
        filterChain.put("/assert/**","anon");
        //放行webjars
        filterChain.put("/webjars/**","anon");
        //放行druid
        filterChain.put("/druid/**", "anon");
        //登出放行
        filterChain.put("/logout", "anon");
        filterChain.put("/register","anon");
        filterChain.put("/", "anon");
        //除上述url外都必须认证通过才能访问, 为通过认证自动访问setLoginUrl()设置的url
        filterChain.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChain);
        return shiroFilterFactoryBean;
    }

    /**
     * 关联Realm
     * @param userRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(Realm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 用户认证与权限认证
     * @param matcher 指定Shiro加密方式
     */
    @Bean
    public Realm userRealm() {
        CustomizeRealm realm = new CustomizeRealm();
        //告诉Shiro使用MD5加密
        realm.setCredentialsMatcher(MD5());
        return realm;
    }

    /**
     *  MD5加密
     */
    private HashedCredentialsMatcher MD5() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //指定加密方式为MD5
        matcher.setHashAlgorithmName("MD5");
        //加密次数
        matcher.setHashIterations(5);
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }
    /**
     * 开始shiro标签
     */
    @Bean
    public ShiroDialect dialect() {
        return new ShiroDialect();
    }

#自定义Realm

@Slf4j
public class CustomizeRealm extends AuthorizingRealm {
    @Autowired
    private IUserService userService;

    /**
     * 获取用户权限和角色
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 登入认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        //获取用户名
        String username = usernamePasswordToken.getUsername();
        User user = userService.queryUser(username);
        //判断数据库中是否有该用户
        if (ObjectUtils.isEmpty(user)) {
            //如果不存在抛出用户不存在异常
            throw new UnknownAccountException();
        }
        //获取加密后的密码
        String password = user.getPassword();
        //盐值
        ByteSource salt = ByteSource.Util.bytes(username);
        //密码校验由shiro完成
        //principal,hashedcredentials,salt,realName
        SimpleAuthenticationInfo info =
                //校验加密后的密码盐值是否匹配
                new SimpleAuthenticationInfo(username,
                        password,
                        salt,
                        getName());
        return info;
    }
}

#加密工具类

/**
 * MD5加密工具类
 */
public class MD5Utils {
    //加密方式可以时SHA1或是MD5
    private static final String ALGORITHM_NAME = "MD5";
    private static final int HASH_ITERATIONS = 5;
    private static final String SALT = "chz";

    //加密方式,加密对象,盐值,加密次数
    public static String encrypt(String password, String username) {
        SimpleHash hash = new SimpleHash(ALGORITHM_NAME, password, username, HASH_ITERATIONS);
        return hash.toHex();
    }

    public static String encrypt(String password) {
        SimpleHash hash = new SimpleHash(ALGORITHM_NAME, password, SALT, HASH_ITERATIONS);
        return hash.toHex();
    }
}

#controller

@Slf4j
@Controller
public class UserController {
    @Autowired
    IUserService userService;

    @GetMapping("/login")
    public String toLogin() {
        return "login";
    }
    @ResponseBody
    @PostMapping("/register")
    public String register(@RequestBody User user){
        String password = MD5Utils.encrypt(user.getPassword(),user.getName());
        user.setPassword(password);
        userService.save(user);
        return "注册成功";
    }
    @ResponseBody
    @PostMapping(value = "/login")
    public String login(@RequestBody User user) {
        log.info("在执行用户认证时调用了数据库,原因不明");
        //获取当前用户
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token =
                new UsernamePasswordToken(user.getName(), user.getPassword());
        try {
            currentUser.login(token);
            //@ResponseBody返回的如果是一个字符串,直接写到客户端;如果是对象才会将对象转为json串
            //如果登入成功,返回0通过ajax判断重定向
            return "0";
            //为了安全不应该具体的显示错误
        } catch (UnknownAccountException e) {
            System.out.println("用户名错误");
            return "用户名错误或密码错误";
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误");
            return "用户名错误或密码错误";
        }
    }
原文地址:https://www.cnblogs.com/kikochz/p/12827752.html